先大致的了解一下题目环境
看到题目后先尝试注册一个账号


发现直接跳转到购买(buy)界面
随意输入7个数据(框中提示输入7个数据)

发现余额居然涨了5块钱

再看看其他界面
Accunt

Claim Your Prize

在这里可以了解到我们只需要存够9990000$就能购买flag
这时候解题思路大概就出来了
首先我们要通过抽奖存钱
然后通过存下的钱来购买flag
所以我们要先知道抽中大奖(也就是7个全中)的逻辑是什么
(这个时候我也想到了为什么不能一直抽奖,抽够钱了也可以买flag,于是抽了一段时间后我出现了一个弹窗打消了我的念头)

所以还是老老实实找别的漏洞吧
但通过对buy页面的源代码分析我们似乎得不到任何线索
用dirsearch扫描后发现存在.git泄露

用GitHack获取.git文件

因为文件中内容比较多,所以就不一个文件一个文件的讲解了,这里只简单的概述一下每个文件大概完成了什么功能:
目标页面构成如下:
- index.php -> 首页,显示游戏规则
- register.php -> 注册页面,用于注册用户后购买彩票
- buy.php -> 彩票抽奖页面,在此页面可以通过抽奖获得虚拟币
- account.php -> 用户账户页面,用于显示用户的账户余额
- market.php -> 领奖页面,通过虚拟币购买我们需要的 flag
- logout.php -> 登出页面,同时销毁用户 SESSION
目标其他文件功能如下:
- api.php -> 所有的逻辑都通过调用此页面完成,包括注册用户,销毁用户,生成彩票随机数,购买 flag 等
- check_register.php -> 用于检查用户是否已经注册,如果未注册,则跳转到注册页面
- config.php -> 配置文件,里面包含了 flag
- footer.php -> 页脚
- header.php -> 页头
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;
}
其中在关键在于这里存在一个弱比较漏洞
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]){ //通过 $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]);
}
到这里我们想要达到抽中大奖的思路大体成型了
要抽中大奖,需构造用户号码与之弱匹配让 7 个位置的号码均满足 $numbers[$i] == $win_numbers[$i]。
在这里我们先补充一下弱比较漏洞的知识
弱等于 ==
在比较前会先把两种字符串类型转成相同的再进行比较。简单的说,它不会比较变量类型,只比较值。
达成条件
1.若一个数字和一个字符串进行比较或者进行运算
PHP 会把字符串转换成数字再进行比较。若字符串以数字开头,则取开头数字作为转换结果(例如 “aaa” 是不能转换为数字的字符串,而 “123” 或 “123aa” 就是可以转换为数字的字符串),而对于不能转换为数字的字符串或 null,则转换为0;
2.布尔值 true 和任意字符串都弱相等

3.数字和“e“开头加上数字的字符串(例如”1e123”)会当作科学计数法去比较(次方);
同时留意0eXXXXX类型的字符串,类型转换机制会把它识别为一个科学计数法表示的数字“0”,所以无论0e后面是什么,0 的多少次方结果还是 0
这里我们利用的就是第二个特性(布尔值 true 和任意字符串都弱相等)
打开burp,抓取抽奖时的包
(因为我们修改的是抽奖的逻辑,在进行抽奖的时候达到7个球都判断为ture,故修改的是抽奖时的包)(记得先登录)
点击buy抓取抽奖时的包

(意外发现这个文件刚好也是我们刚刚分析的文件,所以大家也可以在一开始通过抓包寻找抽奖时调用的是哪个php文件,然后再回头分析抽奖的逻辑)

修改number的值,这里我们可以利用列表来统一化为ture

多点几次就够钱买了(所以我的flag怎么这么抽象)

6690

被折叠的 条评论
为什么被折叠?



