彼は、PHP-監査-Labsのコード監査Day8の練習の[赤]チームに始まった
リンク:https://github.com/hongriSec/PHP-Audit-Labs
練習に行くことができます興味のある学生
予備知識:
コンテンツのタイトルが来るPHPセキュリティ2017 CALENDAR
。8日目-次のようにキャンドルのコードは次のとおりです。
header("Content-Type: text/plain");
function complexStrtolower($regex, $value) {
return preg_replace(
'/(' . $regex . ')/ei',
'strtolower("\\1")',
$value
);
}
foreach ($_GET as $regex => $value) {
echo complexStrtolower($regex, $value) . "\n";
}
脆弱性分析:
この質問は検査されpreg_replace
た機能の使用の/e
コード実行の問題につながる、パターンを。我々は、上記のコードことが判明第11行
でGET
要求内のパラメータ実施態様来complexStrtolower
機能、および変数$regex
と$value
、コードの実行モードの存在下で使用されるpreg_replace
機能。そこで、我々は、制御することができますpreg_replace
機能を第1个
、 第3个参数
するためにコードを実行します。
preg_replace()関数:
(PHP 4、PHP 5、PHP 7)
特長:
preg_replace関数は、正規表現検索および置換を実行します。
定義:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]])
検索subject
マッチングpattern
する部分をreplacement
交換すること。
説明:
パラメータ | 説明 |
---|---|
パターン | パターンを検索するには、それは文字列または文字列の配列にすることができます |
置換 | 文字列または文字列の選択肢の配列 |
主題 | 置き換え文字列のターゲット文字列や配列を検索します。 |
限定 | 代替的に、各被験者の代替文字列の各パターンのため倍の最大数。デフォルトは-1(無制限) |
カウント | あるいは、置換の数は、実行されます。 |
$pattern
プレゼンス/e
コードの実行を許可するモード修飾子、/e
パターン修飾子を、ある**preg_replace() **
に$replacement
ようphp
に実行するためのコード
戻り値:
場合はsubject
、配列は、preg_replace()
リストを返し、それ以外の文字列を返します。
一致が確認された場合は、交換をした後、subject
返され、それ以外の場合にはそのまま返されますsubject
。エラーが発生した場合、リターンNULL
。
例:
しかし、コードとして実行することができる第2个参数
が、固定されました'strtolower("\\1")'
。ここではそれがどこ\ 1つまり、参照をバック正規表現の知識に来る時には、我々は、参照することができW3Cschoolの説明を:
後方参照:
パターンの正規表現パターンまたは一部に两边添加圆括号
関連する原因となり匹配存储到一个临时缓冲区
、各サブマッチングが捕捉右正規表現パターンで表示され、左から順に格納されます。以下からのバッファ番号1
の開始は、あなたがするまで保存することができ99
、サブ表現をキャプチャします。各バッファを使用することができる'\n'
アクセスにn
一つまたは2桁の10進数の特定のバッファを識別することです。
公式のタイトルがする
payload :/?.*={${phpinfo()}}
GETリクエストパラメータ名不正な文字場合、PHPは、アンダースコアに置き換えられますので、実際に、使用されていない、それは.*
なります_*
。ここでは、使用可能な提供payload :\S*=${phpinfo()}
、詳細な分析を、記事を参照してください。綿密な調査とコードにpreg_replaceの実装
ケーススタディ:
ケーススタディでは、我々は選択し
CmsEasy 5.5
たバージョンを。
セキュリティツールを提供するために、脆弱性のPOCサイト、手続き(メソッド)のみご自身の責任で安全研究と教育の目的のために、攻撃運ぶことができます!
脆弱性分析:
漏洞入口文件为/lib/tool/form.php
(84-94行),我们可以看到下面第7行处引用了preg_replace
:
function getform($name,$form,$field,$data) {
if (get('table') &&isset(setting::$var[get('table')][$name]))
$form[$name]=setting::$var[get('table')][$name];
if (get('form') &&isset(setting::$var[get('form')][$name]))
$form[$name]=setting::$var[get('form')][$name];
if (isset($form[$name]['default']))
$form[$name]['default']=preg_replace('/\{\?([^}]+)\}/e',"eval('return $1;')",$form[$name]['default']);
if (!isset($data[$name]) &&isset($form[$name]['default']))
$data[$name]=@$form[$name]['default'];
if (preg_match('/templat/',$name) &&empty($data[$name]))
$data[$name]=@$form[$name]['default'];
且使用了/e
模式。如果 $form[$name]['default']
的内容被正则匹配到,就会执行 eval
函数,导致代码执行。
我们再来看看这个getform()
函数在何处被引用。通过搜索,我们可以发现在 Cache/template/default/manage/add.html
程序中,调用了此函数。这里我们需要关注catid
(下面 第4行
代码),因为 catid
作为 $name
在preg_preolace()
函数中使用到,这是我们成功利用漏洞的关键。 add.html
中的关键代码如下:
<div class="hid_box">
<strong>{lang(addcategory)}</strong>
<div class="hbox" style="background:none;">
{form::getform('catid',$form,$field,$data)}
</div>
</div>
那么问题来了, catid
是在何处定义的,或者说与什么有关?通过搜索,我们发现 lib/table/archive.php
文件中的 get_form()
函数对其进行了定义。
如图所示,我们可以看到该函数 return
了一个数组,数组里包含了catid
、 typeid
等参数对应的内容。仔细查看,发现其中又嵌套着一个数组。在 第6行
处 发现了 default
字段,这个就是我们上面提到的$form[$name]['default']
。
而上图 第6行
的 get()
方法在lib/tool/front_class.php
中,它是程序内部封装的一个方法。可以看到根据用户的请求方式, get()
方法会调用 front
类相应的 get
方法或 post
方法,具体代码如下:
front
类的 get
方法和 post
方法如下,看到其分别对应静态数组:
继续跟进静态方法 get
和post
,可以看到在 front
类中定义的静态属性:
这就意味着前面说的 $form[$name]['default']
中 name
和 default
的内容,都是我们可以控制的。
我们屡一下思路,get_form
函数定义了catid
的值, catid
对应的 default
字段又存在代码执行漏洞。而catid
的值由 get('catid')
决定,这个 get('catid')
又是用户可以控制的。所以我们现在只要找到调用 get_form
函数的地方,即可触发该漏洞。通过搜索,我们发现在 /lib/default/manage_act.php
文件的第10行
调用了 get_form()
函数,通过 View
模板直接渲染到前台显示:
这就形成了这套程序整体的一个执行流程,如下图所示:
漏洞利用:
1、首先打开首页,点击游客投稿
2、进入到相应的页面,传给catid
值,让他匹配到 /\{\?([^}]+)\}/e
这一内容,正则匹配的内容也就是{?(任意内容)}
,所以我们可以构造payload: catid={?(phpinfo())}
修复建议:
漏洞是 preg_replace()
存在/e
模式修正符,如果正则匹配成功,会造成代码执行漏洞,因此为了避免这样的问题,我们避免使用 /e
模式修正符,如下图第7行:
结语
再次感谢【红日团队】