攻防世界Web 高手进阶区:Lottery

tech2025-12-22  0

前言

这其实是我学了CTF的Web方向一个月以来做的第一道关于git泄露的题,之前发了几篇关于CTFHub中git泄露的题目,是因为我遇到这题的时候完全知识盲区,去补了git知识然后相应的做了CTFHub的题目之后才敢来继续做这题。总的来说,这题还是很好的,考了git泄露的知识点,考了代码审计,考了php弱类型比较的知识点,还考了burp抓包相关的知识,这题虽然前前后后算上学习居然有一天,但是对于我这样的小白来说,收获还是巨大的。

WP

打开题目发现是个猜数字游戏,登录之后玩了玩发现买flag遥不可及,直接dirsearch扫描,发现存在git泄露。 使用githack之后如果文件夹中除了.git文件没有其他文件,就需要在bash里使用git checkout-index -a,这样就可以看到网页的各种php文件了。 然后就是代码审计,我把所有php文件都看了一遍,除去看不懂的JavaScript,大致的逻辑便懂了,但是我这里犯了一个特别重大的错误,因为我以前C语言写的比较多,而且是刚学Web一个月,接触的PHP代码不多,虽然以前遇到过PHP中弱类型比较的相关问题,但是我一看到==,就下意识按照C语言的那种思路,觉得就是判断是不是相等,没有往弱类型比较上面想,导致我接下来的全都走偏了。

错误解法: 我把整个文件复制到了我自己的wamp下面,然后开wamp打开这个文件在本地运行,我把api里面的代码做了修改,无论数字有几个相等,都获得最大金额,然后买flag,出现了这个情况:

this is not the real flag这句话我在config.php里面看到过,当时我看到的时候以为这只是迷惑我的,并不是真正的buy那里的接口,我觉得git总能以某种我不知道的方法让我获得flag.事实证明我错了,还错的很离谱。为此我一定要把这次错误的经历写下来,让自己以后少犯这样的错误。

正确解法: 我们首先需要对api.php文件进行代码审计

<?php require_once('config.php'); header('Content-Type: application/json'); function response($resp){ die(json_encode($resp)); } function response_error($msg){ $result = ['status'=>'error']; $result['msg'] = $msg; response($result); } function require_keys($req, $keys){ foreach ($keys as $key) { if(!array_key_exists($key, $req)){ response_error('invalid request'); } } } function require_registered(){ if(!isset($_SESSION['name']) || !isset($_SESSION['money'])){ response_error('register first'); } } function require_min_money($min_money){ if(!isset($_SESSION['money'])){ response_error('register first'); } $money = $_SESSION['money']; if($money < 0){ $_SESSION = array(); session_destroy(); response_error('invalid negative money'); } if($money < $min_money){ response_error('you don\' have enough money'); } } if($_SERVER["REQUEST_METHOD"] != 'POST' || !isset($_SERVER["CONTENT_TYPE"]) || $_SERVER["CONTENT_TYPE"] != 'application/json'){ response_error('please post json data'); } $data = json_decode(file_get_contents('php://input'), true); if(json_last_error() != JSON_ERROR_NONE){ response_error('invalid json'); } require_keys($data, ['action']); // my boss told me to use cryptographically secure algorithm function random_num(){ do { $byte = openssl_random_pseudo_bytes(10, $cstrong); $num = ord($byte); } while ($num >= 250); if(!$cstrong){ response_error('server need be checked, tell admin'); } $num /= 25; return strval(floor($num)); } function random_win_nums(){ $result = ''; for($i=0; $i<7; $i++){ $result .= random_num(); } return $result; } function buy($req){ require_registered(); require_min_money(2); $money = $_SESSION['money']; $numbers = $req['numbers']; $win_numbers = random_win_nums(); $same_count = 0; for($i=0; $i<7; $i++){ if($numbers[$i] == $win_numbers[$i]){ $same_count++; } } switch ($same_count) { case 2: $prize = 5; break; case 3: $prize = 20; break; case 4: $prize = 300; break; case 5: $prize = 1800; break; case 6: $prize = 200000; break; case 7: $prize = 5000000; break; default: $prize = 0; break; } $money += $prize - 2; $_SESSION['money'] = $money; response(['status'=>'ok','numbers'=>$numbers, 'win_numbers'=>$win_numbers, 'money'=>$money, 'prize'=>$prize]); } function flag($req){ global $flag; global $flag_price; require_registered(); $money = $_SESSION['money']; if($money < $flag_price){ response_error('you don\' have enough money'); } else { $money -= $flag_price; $_SESSION['money'] = $money; $msg = 'Here is your flag: ' . $flag; response(['status'=>'ok','msg'=>$msg, 'money'=>$money]); } } function register($req){ $name = $req['name']; $_SESSION['name'] = $name; $_SESSION['money'] = 20; response(['status'=>'ok']); } switch ($data['action']) { case 'buy': require_keys($data, ['numbers']); buy($data); break; case 'flag': flag($data); break; case 'register': require_keys($data, ['name']); register($data); break; default: response_error('invalid request'); break; }

重点就是buy函数那里。我们发现(我没发现)

for($i=0; $i<7; $i++){ if($numbers[$i] == $win_numbers[$i]){ $same_count++; } }

这里使用了PHP的弱类型比较,这就导致了漏洞的产生。 PHP中==和 ===是不一样的,==会把比较的双方经过类型转换后再比较,但是

=== 是比较严格的比较,如果类型不同就会返回false。 重点来了,怎么样才能获得最大金额呢?这里使用bool欺骗。

PHP的"=="和JS中有”= ="在进行比较时,如果有true和false参与,规则会不同。

在php中,如果bool和"任何其他类型"比较,"任何其他类型"会转换为bool。

在JS中,

如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false转换为0,而true转换为1;如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较

在本题中,使用加密然后随机来产生七位数字,这是在一个字符串中的,然后一个一个的取出进行比较,那么我们还需要知道下面这个知识点: PHP中 当转换为 boolean 时,以下值被认为是 FALSE : 布尔值 FALSE 本身 整型值 0(零) 浮点型值 0.0(零) 空字符串,以及字符串 “0” 不包括任何元素的数组(注意,一旦包含元素,就算包含的元素只是一个空数组,也是true) 不包括任何成员变量的对象(仅 PHP 4.0 适用) 特殊类型 NULL(包括尚未赋值的变量) 从空标记生成的 SimpleXML 对象 所有其它值都被认为是 TRUE (包括任何资源)。

因此,根据上面所说的,挨个比较的时候,如果使用bool欺骗,那么例如字符串"1","2"等,都将转换为true与我们输入的bool值进行比较,这样就可以利用这个漏洞。

因此,我们先随便输入7个数字,然后burp抓包,将numbers改为全为true的。

这里还需要注意的是我们看到content-type里面的是application/json。 application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。 而json又是支持bool类型的,因此可以修改提交,然后多次提交然后买flag就可以了。

后记

这题其实关键还是PHP弱类型比较。PHP弱类型比较的题型还是比较多的,网上可以看到很多,但是单纯的看也没什么收获,只能这样遇到一次这样的题目记住一次方法,这样不断积累,对于PHP弱类型比较会掌握的比较好了。

最新回复(0)