SQL注入的本质-是什么数据库

N0va7
2025-09-10 / 0 评论 / 3 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2025年09月10日,已超过266天没有更新,若内容或图片失效,请留言反馈。

SQL注入的基础代码

以下知识仅为个人学习过程中的记录
<?php
  $db = init_db();
  $username = $_GET['username']; //input: 111' and 1=1#
  $db->query("select * from table where username = '$username'");
?>
string connectionstring = "xxx";
SqlConnection con = new SqlConnection(connectionstring);
con.Open();
string idCard = Request.QueryString["idCard"];
string sql = "select * from table where idCard = '" + idCard + "'";
SqlDataAdapter adapter = new SqlDataAdapter(sql, con);
DataSet dataSet = new System.Data.DataSet();
adapter.Fill(dataSet);
con.Close()
conn = DBHerpel.getConnection();
if (conn == null)
    return;
String username = request.getParameter("username");
String Sql = "select * from table where username = '" + username + "'";
stt = conn.createStatement();
set = stt.executeQuery(Sql);
@app.route("/", methods=["GET"])
def test():
    username = request.args.get('username')
    sql = "select * from table where username = '" + username + "'"
    conn = connect(host='localhost',port=3306,user='root',password='',database='test',charset='utf8')
    cs1 = conn.cursor()
    count = cs1.execute(sql)

哪怕不是全部了解上面的语言,也可以看出来,这些代码都是没有使用ORM框架,直接调用原生数据库操作函数的实例。(ORM框架其实本身也是调用原生数据库操作函数,但是它做了一些封装和映射,只要按照规范写法,现代ORM基本都加了一些过滤在里面)

通过上面的代码,可以很明显看出来,这些sql语句都是拼接构成的,所以说他们都存在SQL注入,在数据库层面中的数据流(SQL语句)侵入到了控制流。通常这种漏洞点大概率会在不同层面组件的交汇点,像上面就是代码层和数据库层的交汇点出现了SQL注入。

调用ORM的错误写法

PHP

对于PHP来说没有什么特别出名的ORM框架,基本都是各个Web开发框架自带的ORM。对于这种情况,可以直接审计对应Web开发框架,因为这些开发框架/CMS的ORM可能写的并不规范,没有专门针对这方面进行维护,所以说相对而言存在漏洞点的可能更高

Java/Mybatis

Mybatis需要有一个xml配置文件来绑定映射关系

<select id="findUserByName" parameterType="java.lang.String" resultType="cn.itcast.mybatis.po.User">
 <!-- 拼接 MySQL,引起 SQL 注⼊ -->
 SELECT * FROM table WHERE username = '${value}'
</select>
@Test
public void testFindUserByName() throws Exception{
    SqlSession sqlSession=sqlSessionFactory.openSession();
    //创建UserMapper代理对象
    UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
    //调⽤userMapper的⽅法
    List<User> list=userMapper.findUserByName("fuck' and 1=1#");
    sqlSession.close();
    System.out.println(list);
}

Mybatis存在两种变量绑定方式,分别是#{}${}

  • #是绑定变量的形式,底层会将#{}替换为?,有参数映射会在DefaultParameterHandler中进⾏设置占位符的操作,这就是预编译
  • $也是绑定变量的形式,{value}是直接被替换成对应的值,没有参数映射,不会进行设置占位符的操作,本质上就是拼接

在代码审计中可以直接搜$符号,针对小型的CMS来说简单而有效

Python/Sqlalchemy

Sqlalchemy是Flask最经常配套使用的ORM框架,在许多的Django项目中也经常使用,是目前Python语言中最火的ORM框架

@app.route("/", methods=["GET"])
def test():
 username = request.args.get('username')
 res = db.session.query(table).filter("username={}".format(username))

这个存在漏洞的原因是跟上面的Mybatis差不多,就是先"username={}".format(username)拼接字符串,然后再使用ORM框架进行查询,这个地方就是不同层面的交汇处,因为不规范的写法导致SQL注入

正规写法如下:

@app.route("/", methods=["GET"])
def test():
 username = request.args.get('username')
 res = db.session.query(table).filter(table.username == username)

SQL注入到底是在干什么

重要核心

看完了上面的原生数据库操作函数的调用导致的SQL注入以及ORM框架的错误写法导致的SQL注入,我们知道SQL注入到底是在干什么吗?

不需要特别关心SQL注入的类型有哪些,像什么联合查询注入、二次注入、布尔盲注等

其实,仔细总结上面的内容,可以得出一个结论:

SQL注入就是去执行一段SQL语句,而这个SQL语句长什么样子,关键跟数据库类型有关

不同的编程语言他们最终的目的都是将SQL语句送入数据库层进行执行,我们只要能发现注入点即可,至于用什么开发语言都是一样的

对于注入类型

注入类型相对于开发语言来说比较重要一些,不需要特别关注,但是我们得知道一些关键点

能否堆叠注入

像上面提到的,一条SQL语句最终会被解析成一段控制序列

action(动作): select
object(对象): table
subject(⽬标客体): *
condition(条件):
 key: username
 value: $username // ⽤户输⼊

如果能够使用堆叠注入的话,那么我们就会跳出action的动作,进而执行其他动作,这样造成的危害程度肯定会更高

但是堆叠注入的出现越来越少,一般情况下:

Mssql数据库的堆叠注入最多;Oracle数据库的数量少一些;Mysql不多

爆数据的速度

报错~=联合查询>带外查询>布尔盲注>时间盲注

对于输入点

当我们发现一个注入点,我们也要关注这个注入点在SQL语句中的输入位置,根据不同的输入点,我们需要采用不同的payload,同时也要考虑是否需要进行绕过,后面的内容会联动这点

select $username$,password from $table$ where $username2$ = '$xxx$' order by $username3$ desc limit $0$,1

有撸点的SQL注入类型

宽字节注入

概念

我们知道字节是计算机存储世界中最⼩的衡量单位,1Byte = 8bits。所以⼀个字节最⼤能够表示2^8=256个字符。

所以:

对ascii编码⽽⾔,⼀个字符⽤⼀个字节就可以表示,所以ascii编码最多可以表示256个字符。

对GBK编码⽽⾔,⼀个汉字字符需要⽤两个字节表示。所以gbk编码理论上最多可以表示256*256个字符。

利用方式

<?php
  $db = init_db();
  $db->query("SET NAMES 'gbk');  //设置gbk字符集
  // addslashes会自动对单引号转义
  $username = addslashes($_GET['username']);  //input: fuck' and 1=1#
  $db->query("select * from table where username = '$username'");
?>
select * from table where username = 'fuck\' and 1=1#'

SQL语句如上所示,在代码层会被直接插入一个\符号,从而对单引号进行转义,这样我们就没法入侵到控制流了

但是当我们输入是这样的时候,就会出现不一样的效果了

fuck%df\' and 1=1#
数据在存储时采用字节方式存储,但是解读时采用字符标准去解读。
而字符标准跟采用什么字符集有关系。所以在连接数据库执行SQL语句时(这时候就是对数据的解读),会按照字符集编码的规则去解读。而当遇到\xDF\x5C时,就会解读成

c62d964cbf1179113b90b0b8b9d1cb4f_MD5

转码网站:https://www.23bei.com/tool/54.html#

\x5c就是\的十六进制表示

a42e17f4bde6996e56c8e4468adac6aa_MD5

这里补充一下,并非只有\xdf才可以和\x5c组成字符,其他字符也OK,不必纠结这个,具体哪些字符组合可以自行查阅GBK编码

情景利用

所以在代码审计中审阅宽字节注入的方式就是查看数据库连接文件(一般是conn.php这种)。

检查其字符集是否为utf-8,如果不是utf-8,那么就有可能产生注入

参考文章

https://xz.aliyun.com/news/1477?time__1311=eqIx9DBDnGDQitD%2F82xBKcAOzddx2AbD&u_atoken=2997a913f8636a69d9eae6cbcac41657&u_asig=1a0c380917410698476814249e0038

预编译模式下的注入

宽字节注入

<?php
 $username = $_GET['username'];
 $db = "mysql:host=127.0.0.1;dbname=test;charset=gbk";
 $dbname = "root";
 $passwd = "root";
 $conn = new PDO($dbs, $dbname, $passwd);
 $conn->query('SET NAMES GBK');
 $stmt = $conn->prepare("select * from table where username = :username");
 $stmt->bindParam(":username",$username);
 $stmt->execute();
?>

看似进行预编译好像安全了,但是在这种情况下是无法解决宽字节注入的问题的

因为,在数据的流转中是这样的

$stmt = $conn->prepare("select * from table where username = :username");

==>sql = "select * from table where username = '${addslashes($input)}'"

在此处我们有发现了addslashes这个函数,那不是跟传统的宽字节注入绕过方法一样吗?

所以在这种情况下,预编译是不起作用的

一些无法预编译的点

既然讲到预编译不起作用,那么这部分聊聊那些没法通过ORM/预编译去保护的输入点,这种情况是通用的,无视任何语言

like&in关键字

我们知道sql语句的模糊查找⾥⾯⽤的关键字like

like关键字默认是不会预编译的(如果使⽤Mybatis则是预编译报错)。数据库⽅给出的原因好像是like预编译会造成慢查询和DOS

所以对于like关键字,只能通过手动添加预编译来解决。

一些开发只会照着以往的样子进行编写,不会知道like关键字的预编译会失效,所以这时候就给了我们可趁之机

<?php
  $username = $_GET['username'];
  $db = "mysql:host=127.0.0.1;dbname=test;";
  $dbname = "root";
  $passwd = "root";
  $conn = new PDO($dbs, $dbname, $passwd);
  $conn->query('SET NAMES GBK');
  $stmt = $conn->prepare("select * from table where username like '%:username%'");  //不⽣效
  $stmt = $conn->prepare("select * from table where username like concat('%',:username,'%'");  //⽣效
  $stmt->bindParam(":username",$username);
  $stmt->execute();
?>

一些Java开发发现Mybatis编译报错,他就会改用$符号,或者直接就不写预编译。

如果开发手写预编译的话,也可能没有那么完善,存在绕过的机会。

同样的,in关键字也是默认不能进行预编译的,需要在预编译语法中去for循环,有些开发为了方便,写的代码少一些,就可能在这边偷工减料

不能加引号的关键字

结合我们上面看到宽字节绕过预编译,我们可以看到预编译+绑定变量的效果大概有两个步骤

$str = addslashes($input)    //内容转义
$sql = "select * from table where column = '$str'"    //强制使用单引号包裹

结合我们上面提到的产生注入的输入点

select $username$,password from $table$ where $username2$ = '$xxx$' order by $username3$ $desc$ limit $0$,1

观察上面SQL语句中的输入点,有哪些地方是必然不能加单引号的呢?如果这个地方不能加单引号,那么就无法进行预编译

于是整合如下:

$username$,$table$,$username2$,$username3$,$desc$,$0$

也就是说列名、表名、limit子句、order by[desc/asc]都不能添加单引号,所以他们都不能进行预编译。

在这种情况下,开发可能会遗漏这些点的检测,导致存在注入。

参考文章

https://forum.butian.net/share/1559

http://cn-sec.com/archives/286656.html

https://codeantenna.com/a/WzaLCig9qw

权限认知-数据平面

一些权限的例子

  • cms/web层⾯:web后台管理员权限>web前台会员权限>游客权限
  • 在数据库层⾯:root⽤户/sa⽤户/postgres⽤户>普通杂七杂⼋⽤户
  • 在操作系统层⾯:root > 所有其他⽤户

对于权限的重要认知

认清数据平面

未授权RCE

我们以游客的权限,拿到⼀个rce漏洞(也就是未授权rce),那么我们的权限是什么?

是web后台管理员权限吗?并不是,而是操作系统权限此时我们的数据平面在操作系统层面

在操作系统层⾯,我们的**bash进程是httpd进程派⽣的,所以与httpd进程是同层级的,所以此
时我可以读写web⽬录的任意⽂件(前提是web⽬录本身对httpd可读写)**

SQL注入

如果我们以游客权限,拿到⼀个sql注⼊点,那么我们的权限是什么?

是web后台管理员权限吗?并不是,而是数据库权限,此时我们的数据平面在数据库层面

在数据库层⾯,我们⽬前就是mysql进程维护的⼀个通信线程,我们就是数据库的root或者普通库权限(取决于web数据库配置⽂件中定义的⽤户)。

SQL注入到命令执行

如果我们以游客权限,拿到⼀个sql注⼊点,并且这个注⼊点可以执⾏命令,那么我们的权限是什么?

是数据库的root吗?并不是,而是操作系统权限,此时我们的数据平面在操作系统层面

在操作系统层⾯,我们⽬前就是mysql进程派⽣的⼀个bash进程,所以与mysql进程是同层级的,所以此时我可以读写mysql数据库⽂件⽬录的⽂件。也⼤概率有权限读web⽬录的⽂件

0

评论 (0)

取消