Typo3 CVE-2019-12747 反序列化漏洞分析
作者:mengchen@知道创宇404实验室
时间:2019年8月1日
英文版本:https://paper.seebug.org/997/
1. 前言
TYPO3是一个以
PHP编写、采用
GNU通用公共许可证的自由、开源的内容管理系统。
2019年7月16日,
RIPS的研究团队公开了
Typo3 CMS的一个关键漏洞详情,
CVE编号为
CVE-2019-12747,它允许后台用户执行任意
PHP代码。
漏洞影响范围:
Typo3 8.x-8.7.26 9.x-9.5.7。
2. 测试环境简述
Nginx/1.15.8 PHP 7.3.1 + xdebug 2.7.2 MySQL 5.7.27 Typo3 9.5.7
3. TCA
在进行分析之前,我们需要了解下
Typo3的
TCA(Table Configuration Array),在
Typo3的代码中,它表示为
$GLOBALS['TCA']。
在
Typo3中,
TCA算是对于数据库表的定义的扩展,定义了哪些表可以在
Typo3的后端可以被编辑,主要的功能有
- 表示表与表之间的关系
- 定义后端显示的字段和布局
- 验证字段的方式
这次漏洞的两个利用点分别出在了
CoreEngine和
FormEngine这两大结构中,而
TCA就是这两者之间的桥梁,告诉两个核心结构该如何表现表、字段和关系。
TCA的第一层是表名:
$GLOBALS['TCA']['pages'] = [ ... ]; $GLOBALS['TCA']['tt_content'] = [ ... ];
其中
pages和
tt_content就是数据库中的表。
接下来一层就是一个数组,它定义了如何处理表,
$GLOBALS['TCA']['pages'] = [ 'ctrl' => [ // 通常包含表的属性 .... ], 'interface' => [ // 后端接口属性等 .... ], 'columns' => [ .... ], 'types' => [ .... ], 'palettes' => [ .... ], ];
在这次分析过程中,只需要了解这么多,更多详细的资料可以查询官方手册
4. 漏洞分析
整个漏洞的利用流程并不是特别复杂,主要需要两个步骤,第一步变量覆盖后导致反序列化的输入可控,第二步构造特殊的反序列化字符串来写
shell。第二步这个就是老套路了,找个在魔术方法中能写文件的类就行。这个漏洞好玩的地方在于变量覆盖这一步,而且进入两个组件漏洞点的传入方式也有着些许不同,接下来让我们看一看这个漏洞吧。
4.1 补丁分析
从
Typo3官方的通告中我们可以知道漏洞影响了两个组件——
Backend & Core API (ext:backend, ext:core),在GitHub上我们可以找到修复记录:
很明显,补丁分别禁用了
backend的
DatabaseLanguageRows.php和
core中的
DataHandler.php中的的反序列化操作。
4.2 Backend ext 漏洞点利用过程分析
根据补丁的位置,看下
Backend组件中的漏洞点。
路径:
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseLanguageRows.php:37
public function addData(array $result) { if (!empty($result['processedTca']['ctrl']['languageField']) && !empty($result['processedTca']['ctrl']['transOrigPointerField']) ) { $languageField = $result['processedTca']['ctrl']['languageField']; $fieldWithUidOfDefaultRecord = $result['processedTca']['ctrl']['transOrigPointerField']; if (isset($result['databaseRow'][$languageField]) && $result['databaseRow'][$languageField] > 0 && isset($result['databaseRow'][$fieldWithUidOfDefaultRecord]) && $result['databaseRow'][$fieldWithUidOfDefaultRecord] > 0 ) { // Default language record of localized record $defaultLanguageRow = $this->getRecordWorkspaceOverlay( $result['tableName'], (int)$result['databaseRow'][$fieldWithUidOfDefaultRecord] ); if (empty($defaultLanguageRow)) { throw new DatabaseDefaultLanguageException( 'Default language record with id ' . (int)$result['databaseRow'][$fieldWithUidOfDefaultRecord] . ' not found in table ' . $result['tableName'] . ' while editing record ' . $result['databaseRow']['uid'], 1438249426 ); } $result['defaultLanguageRow'] = $defaultLanguageRow; // Unserialize the "original diff source" if given if (!empty($result['processedTca']['ctrl']['transOrigDiffSourceField']) && !empty($result['databaseRow'][$result['processedTca']['ctrl']['transOrigDiffSourceField']]) ) { $defaultLanguageKey = $result['tableName'] . ':' . (int)$result['databaseRow']['uid']; $result['defaultLanguageDiffRow'][$defaultLanguageKey] = unserialize($result['databaseRow'][$result['processedTca']['ctrl']['transOrigDiffSourceField']]); } //省略代码 } //省略代码 } //省略代码 }
很多类都继承了
FormDataProviderInterface接口,因此静态分析寻找谁调用的
DatabaseLanguageRows的
addData方法根本不现实,但是根据文章中的演示视频,我们可以知道网站中修改
page这个功能中进入了漏洞点。在
addData方法加上断点,然后发出一个正常的修改
page的请求。
当程序断在
DatabaseLanguageRows的addData方法后,我们就可以得到调用链。
在
DatabaseLanguageRows这个
addData中,只传入了一个
$result数组,而且进行反序列化操作的目标是
$result['databaseRow']中的某个值。看命名有可能是从数据库中获得的值,往前分析一下。
进入
OrderedProviderList的
compile方法。
路径:
typo3/sysext/backend/Classes/Form/FormDataGroup/OrderedProviderList.php:43
public function compile(array $result): array { $orderingService = GeneralUtility::makeInstance(DependencyOrderingService::class); $orderedDataProvider = $orderingService->orderByDependencies($this->providerList, 'before', 'depends'); foreach ($orderedDataProvider as $providerClassName => $providerConfig) { if (isset($providerConfig['disabled']) && $providerConfig['disabled'] === true) { // Skip this data provider if disabled by configuration continue; } /** @var FormDataProviderInterface $provider */ $provider = GeneralUtility::makeInstance($providerClassName); if (!$provider instanceof FormDataProviderInterface) { throw new \UnexpectedValueException( 'Data provider ' . $providerClassName . ' must implement FormDataProviderInterface', 1485299408 ); } $result = $provider->addData($result); } return $result; }
我们可以看到,在
foreach这个循环中,动态实例化
$this->providerList中的类,然后调用它的
addData方法,并将result作为方法的参数。在调用‘DatabaseLanguageRows‘之前,调用了如图所示的类的‘addData‘方法。![在这里插入图片描述](https://img−blog.csdnimg.cn/2019080210025249.png?x−oss−process=image/watermark,typeZmFuZ3poZW5naGVpdGk,shadow10,textaHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMzgwNTQ5,size16,colorFFFFFF,t70)经过查询手册以及分析代码,可以知道在‘DatabaseEditRow‘类中,通过调用‘addData‘方法,将数据库表中数据读取出来,存储到了‘result作为方法的参数。 在调用`DatabaseLanguageRows`之前,调用了如图所示的类的`addData`方法。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019080210025249.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMzgwNTQ5,size_16,color_FFFFFF,t_70) 经过查询手册以及分析代码,可以知道在`DatabaseEditRow`类中,通过调用`addData`方法,将数据库表中数据读取出来,存储到了`result作为方法的参数。在调用‘DatabaseLanguageRows‘之前,调用了如图所示的类的‘addData‘方法。![在这里插入图片描述](https://img−blog.csdnimg.cn/2019080210025249.png?x−oss−process=image/watermark,typeZmFuZ3poZW5naGVpdGk,shadow10,textaHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMzgwNTQ5,size16,colorFFFFFF,t70)经过查询手册以及分析代码,可以知道在‘DatabaseEditRow‘类中,通过调用‘addData‘方法,将数据库表中数据读取出来,存储到了‘result[‘databaseRow’]
中。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190802100309410.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMzgwNTQ5,size_16,color_FFFFFF,t_70) 路径:typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseEditRow.php:32`
public function addData(array $result) { if ($result['command'] !== 'edit' || !empty($result['databaseRow'])) {// 限制功能为`edit` return $result; } $databaseRow = $this->getRecordFromDatabase($result['tableName'], $result['vanillaUid']); // 获取数据库中的记录 if (!array_key_exists('pid', $databaseRow)) { throw new \UnexpectedValueException( 'Parent record does not have a pid field', 1437663061 ); } BackendUtility::fixVersioningPid($result['tableName'], $databaseRow); $result['databaseRow'] = $databaseRow; return $result; }
再后面又调用了
DatabaseRecordOverrideValues类的
addData方法。
路径:
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordOverrideValues.php:31
public function addData(array $result) { foreach ($result['overrideValues'] as $fieldName => $fieldValue) { if (isset($result['processedTca']['columns'][$fieldName])) { $result['databaseRow'][$fieldName] = $fieldValue; $result['processedTca']['columns'][$fieldName]['config'] = [ 'type' => 'hidden', 'renderType' => 'hidden', ]; } } return $result; }
在这里,将
$result['overrideValues']中的键值对存储到了
$result['databaseRow']中,如果
$result['overrideValues’]可控,那么通过这个类,我们就能控制
$result['databaseRow']的值了。
再往前,看看
$result的值是怎么来的。
路径:
typo3/sysext/backend/Classes/Form/FormDataCompiler.php:58
public function compile(array $initialData) { $result = $this->initializeResultArray(); //省略代码 foreach ($initialData as $dataKey => $dataValue) { // 省略代码... $result[$dataKey] = $dataValue; } $resultKeysBeforeFormDataGroup = array_keys($result); $result = $this->formDataGroup->compile($result); // 省略代码... }
很明显,通过调用
FormDataCompiler的
compile方法,将
$initialData中的数据存储到了
$result中。
再往前走,来到了
EditDocumentController类中的
makeEditForm方法中。
在这里,formDataCompilerInput[′overrideValues′]获取了formDataCompilerInput['overrideValues']获取了formDataCompilerInput[′overrideValues′]获取了this->overrideVals[table]中的数据。而table]中的数据。 而table]中的数据。而this->overrideVals的值是在方法preInitz中设定的,获取的是通过
POST传入的表单中的键值对。
这样一来,在这个请求过程中,进行反序列化的字符串我们就可以控制了。
在表单中提交任意符合数组格式的输入,在后端代码中都会被解析,然后后端根据
TCA来进行判断并处理。 比如我们在提交表单中新增一个名为
a[b][c][d],值为
233的表单项。
在编辑表单的控制器
EditDocumentController.php中下一个断点,提交之后。
可以看到我们传入的键值对在经过
getParsedBody方法解析后,变成了嵌套的数组,并且没有任何限制。
我们只需要在表单中传入
overrideVals这一个数组即可。这个数组中的具体的键值对,则需要看进行反序列化时取的
$result['databaseRow']中的哪一个键值。
if (isset($result['databaseRow'][$languageField]) && $result['databaseRow'][$languageField] > 0 && isset($result['databaseRow'][$fieldWithUidOfDefaultRecord]) && $result['databaseRow'][$fieldWithUidOfDefaultRecord] > 0) { // 省略代码 if (!empty($result['processedTca']['ctrl']['transOrigDiffSourceField']) && !empty($result['databaseRow'][$result['processedTca']['ctrl']['transOrigDiffSourceField']])) { $defaultLanguageKey = $result['tableName'] . ':' . (int) $result['databaseRow']['uid']; $result['defaultLanguageDiffRow'][$defaultLanguageKey] = unserialize($result['databaseRow'][$result['processedTca']['ctrl']['transOrigDiffSourceField']]); } //省略代码 }
要想进入反序列化的点,还需要满足上面的
if条件,动态调一下就可以知道,在
if语句中调用的是
$result['databaseRow']['sys_language_uid'] $result['databaseRow']['l10n_parent']
后面反序列化中调用的是
$result['databaseRow']['l10n_diffsource']
因此,我们只需要在传入的表单中增加三个参数即可。
overrideVals[pages][sys_language_uid] ==> 4 overrideVals[pages][l10n_parent] ==> 4 overrideVals[pages][l10n_diffsource] ==> serialized_shell_data
可以看到,我们的输入成功的到达了反序列化的点。
4.3 Core ext 漏洞点利用过程分析
看下
Core中的那个漏洞点。
路径:
typo3/sysext/core/Classes/DataHandling/DataHandler.php:1453
public function fillInFieldArray($table, $id, $fieldArray, $incomingFieldArray, $realPid, $status, $tscPID) { // Initialize: $originalLanguageRecord = null; $originalLanguage_diffStorage = null; $diffStorageFlag = false; // Setting 'currentRecord' and 'checkValueRecord': if (strpos($id, 'NEW') !== false) { // Must have the 'current' array - not the values after processing below... $checkValueRecord = $fieldArray; if (is_array($incomingFieldArray) && is_array($checkValueRecord)) { ArrayUtility::mergeRecursiveWithOverrule($checkValueRecord, $incomingFieldArray); } $currentRecord = $checkValueRecord; } else { // We must use the current values as basis for this! $currentRecord = ($checkValueRecord = $this->recordInfo($table, $id, '*')); // This is done to make the pid positive for offline versions; Necessary to have diff-view for page translations in workspaces. BackendUtility::fixVersioningPid($table, $currentRecord); } // Get original language record if available: if (is_array($currentRecord) && $GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'] && $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $currentRecord[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0 && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] && (int)$currentRecord[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0 ) { $originalLanguageRecord = $this->recordInfo($table, $currentRecord[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']], '*'); BackendUtility::workspaceOL($table, $originalLanguageRecord); $originalLanguage_diffStorage = unserialize($currentRecord[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']]); } ......//省略代码
看代码,如果我们要进入反序列化的点,需要满足前面的if条件
if (is_array($currentRecord) && $GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'] && $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $currentRecord[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0 && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] && (int)$currentRecord[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0 )
也就是说要满足以下条件
$currentRecord
是个数组- 在
TCA
中$table
的表属性中存在transOrigDiffSourceField、languageField、
transOrigPointerField`字段。 - table‘的属性‘languageField‘和‘transOrigPointerField‘在‘table`的属性`languageField`和`transOrigPointerField`在`table‘的属性‘languageField‘和‘transOrigPointerField‘在‘currentRecord`中对应的值要大于0。
查一下
TCA表,满足第二条条件的表有
sys_file_reference sys_file_metadata sys_file_collection sys_collection sys_category pages
但是所有
sys_*的字段的
adminOnly属性的值都是
1,只有管理员权限才可以更改。因此我们可以用的表只有
pages。
它的属性值是
[languageField] => sys_language_uid [transOrigPointerField] => l10n_parent [transOrigDiffSourceField] => l10n_diffsource
再往上,有一个对传入的参数进行处理的
if-else语句。
从注释中,我们可以知道传入的各个参数的功能:
数组
$fieldArray是默认值,这种一般都是我们无法控制的
数组
$incomingFieldArray是你想要设置的字段值,如果可以,它会合并到
$fieldArray中。
而且如果满足
if (strpos($id, 'NEW') !== false)条件的话,也就是
$id是一个字符串且其中存在
NEW字符串,会进入下面的合并操作。
$checkValueRecord = $fieldArray; ...... if (is_array($incomingFieldArray) && is_array($checkValueRecord)) { ArrayUtility::mergeRecursiveWithOverrule($checkValueRecord, $incomingFieldArray); } $currentRecord = $checkValueRecord;
如果不满足上面的
if条件,
$currentRecord的值就会通过
recordInfo方法从数据库中直接获取。这样后面我们就无法利用了。
简单总结一下,我们需要
$table是
pages
$id是个字符串,而且存在NEW字符串
$incomingFieldArray中要存在
payload
接下来我们看在哪里对该函数进行了调用。
全局搜索一下,只找到一处,在
typo3/sysext/core/Classes/DataHandling/DataHandler.php:954处的
process_datamap方法中进行了调用。
整个项目中,对
process_datamap调用的地方就太多了,尝试使用
xdebug动态调试来找一下调用链。从RIPS团队的那一篇分析文章结合上面的对表名的分析,我们可以知道,漏洞点在创建
page的功能处。
接下来就是找从
EditDocumentController.php的
mainAction方法到前面我们分析的
fillInFieldArray方法的调用链。
尝试在网站中新建一个
page,然后在调用
fillInFieldArray的位置下一个断点,发送请求后,我们就拿到了调用链。
看一下
mainAction的代码。
public function mainAction(ServerRequestInterface $request): ResponseInterface { // Unlock all locked records BackendUtility::lockRecords(); if ($response = $this->preInit($request)) { return $response; } // Process incoming data via DataHandler? $parsedBody = $request->getParsedBody(); if ($this->doSave || isset($parsedBody['_savedok']) || isset($parsedBody['_saveandclosedok']) || isset($parsedBody['_savedokview']) || isset($parsedBody['_savedoknew']) || isset($parsedBody['_duplicatedoc']) ) { if ($response = $this->processData($request)) { return $response; } } ....//省略代码 }
当满足
if条件是进入目标
$response = $this->processData($request)。
if ($this->doSave || isset($parsedBody['_savedok']) || isset($parsedBody['_saveandclosedok']) || isset($parsedBody['_savedokview']) || isset($parsedBody['_savedoknew']) || isset($parsedBody['_duplicatedoc']) )
这个在新建一个
page时,正常的表单中就携带
doSave == 1,而
doSave的值就是在方法
preInit中获取的
这样条件默认就是成立的,然后将
$request传入了
processData方法
public function processData(ServerRequestInterface $request = null): ?ResponseInterface { // @deprecated Variable can be removed in TYPO3 v10.0 $deprecatedCaller = false; ......//省略代码 $parsedBody = $request->getParsedBody(); // 获取Post请求参数 $queryParams = $request->getQueryParams(); // 获取Get请求参数 $beUser = $this->getBackendUser(); // 获取用户数据 // Processing related GET / POST vars $this->data = $parsedBody['data'] ?? $queryParams['data'] ?? []; $this->cmd = $parsedBody['cmd'] ?? $queryParams['cmd'] ?? []; $this->mirror = $parsedBody['mirror'] ?? $queryParams['mirror'] ?? []; // @deprecated property cacheCmd is unused and can be removed in TYPO3 v10.0 $this->cacheCmd = $parsedBody['cacheCmd'] ?? $queryParams['cacheCmd'] ?? null; // @deprecated property redirect is unused and can be removed in TYPO3 v10.0 $this->redirect = $parsedBody['redirect'] ?? $queryParams['redirect'] ?? null; $this->returnNewPageId = (bool)($parsedBody['returnNewPageId'] ?? $queryParams['returnNewPageId'] ?? false); // Only options related to $this->data submission are included here $tce = GeneralUtility::makeInstance(DataHandler::class); $tce->setControl($parsedBody['control'] ?? $queryParams['control'] ?? []); // Set internal vars if (isset($beUser->uc['neverHideAtCopy']) && $beUser->uc['neverHideAtCopy']) { $tce->neverHideAtCopy = 1; } // Load DataHandler with data $tce->start($this->data, $this->cmd); if (is_array($this->mirror)) { $tce->setMirror($this->mirror); } // Perform the saving operation with DataHandler: if ($this->doSave === true) { $tce->process_uploads($_FILES); $tce->process_datamap(); $tce->process_cmdmap(); } ......//省略代码 }
代码很容易懂,从
$request中解析出来的数据,首先存储在
$this->data和
$this->cmd中,然后实例化一个名为
$tce,调用
$tce->start方法将传入的数据存储在其自身的成员
datamap和
cmdmap中。
typo3/sysext/core/Classes/DataHandling/DataHandler.php:735 public function start($data, $cmd, $altUserObject = null) { ......//省略代码 // Setting the data and cmd arrays if (is_array($data)) { reset($data); $this->datamap = $data; } if (is_array($cmd)) { reset($cmd); $this->cmdmap = $cmd; } }
而且
if ($this->doSave === true)这个条件也是成立的,进入
process_datamap方法。
代码有注释还是容易阅读的,在第985行,获取了
datamap中所有的键名,然后存储在
$orderOfTables,然后进入
foreach循环,而这个
$table,在后面传入
fillInFieldArray方法中,因此,我们只需要分析
$table == pages时的循环即可。
$fieldArray = $this->fillInFieldArray($table, $id, $fieldArray, $incomingFieldArray, $theRealPid, $status, $tscPID);
大致浏览下代码,再结合前面的分析,我们需要满足以下条件:
$recordAccess
的值要为true
$incomingFieldArray
中的payload
不会被删除$table
的值为pages
$id
中存在NEW
字符串
既然正常请求可以直接断在调用fillInFieldArray
处,正常请求中,第一条、第三条和第四条都是成立的。
根据前面对fillInFieldArray
方法的分析,构造payload
,向提交的表单中添加三个键值对。
data[pages][NEW5d3fa40cb5ac4065255421][l10n_diffsource] ==> serialized_shell_data data[pages][NEW5d3fa40cb5ac4065255421][sys_language_uid] ==> 4 data[pages][NEW5d3fa40cb5ac4065255421][l10n_parent] ==> 4
其中
NEW*字符串要根据表单生成的值进行对应的修改
发送请求后,依旧能够进入
fillInFieldArray,而在传入的
$incomingFieldArray参数中,可以看到我们添加的三个键值对。
进入
fillInFieldArray之后,其中
l10n_diffsource将会进行反序列化操作。此时我们在请求中将其
l10n_diffsource改为构造好的序列化字符串,重新发送请求即可成功
getshell。
5. 写在最后
其实单看这个漏洞的利用条件,还是有点鸡肋的,需要你获取到
typo3的一个有效的后台账户,并且拥有编辑
page的权限。
而且这次分析
Typo3给我的感觉与其他网站完全不同,我在分析创建&修改
page这个功能的参数过程中,并没有发现什么过滤操作,在后台的所有参数都是根据
TCA的定义来进行相应的操作,只有传入不符合
TCA定义的才会抛出异常。而
TCA的验证又不严格导致了变量覆盖这个问题。
官方的修补方式也是不太懂,直接禁止了反序列化操作,但是个人认为这次漏洞的重点还是在于前面变量覆盖的问题上,尤其是
Backend的利用过程中,可以直接覆盖从数据库中取出的数据,这样只能算是治标不治本,后面还是有可能产生新的问题。
当然了,以上只是个人拙见,如有错误,还请诸位斧正。
本文由 Seebug Paper 发布,如需转载请注明来源。
欢迎关注我,我将定期搬运技术文章~
想了解更多咨询,可以关注我们的官网和微信公众号~~
参考链接
- Confluence 未授权 RCE (CVE-2019-3396) 漏洞分析
- CVE-2016-1000031 Apache Commons FileUpload 反序列化漏洞深入分析
- CVE-2016-1000031 Apache Commons FileUpload 反序列化漏洞深入分析
- CVE-2014-7911 Android 反序列化漏洞分析
- WebLogic CVE-2019-2647、CVE-2019-2648、CVE-2019-2649、CVE-2019-2650 XXE漏洞分析
- Confluence 未授权 RCE (CVE-2019-3396) 漏洞分析
- CVE-2019-11477 SACK Panic漏洞利用分析
- CVE-2019-11581:JIRA服务器模板注入漏洞警报
- 利用Vulnhub复现漏洞 - Joomla 3.4.5 反序列化漏洞(CVE-2015-8562)
- CVE-2017-9805:Struts2 REST插件远程执行命令漏洞(S2-052) 分析报告
- CVE-2016-10190 FFmpeg Http协议 heap buffer overflow漏洞分析及利用
- CVE-2012-0158 MSCOMCTL控件漏洞分析
- CVE-2017-9805:Struts2 REST插件远程执行命令漏洞(S2-052) 分析报告
- CVE-2019-14234:Django JSONField/HstoreField SQL注入漏洞警报
- [EXP]CVE-2019-1821 Cisco Prime Infrastructure思科未授权远程代码执行漏洞
- CVE-2010-3971 CSS内存破坏漏洞分析
- CVE-2019-11477漏洞详解详玩(删)
- Smarty <= 3.1.32 PHP代码执行漏洞分析—【CVE-2017-1000480】
- CVE-2017-9805:Struts2 REST插件远程执行命令漏洞(S2-052) 分析报告
- 漏洞分析之CVE-2010-2883(栈溢出)