sql注⼊的基础代码样例

php:

1
2
3
$db = init_db();
$username = $_GET['username']; //input: fuckdada' and 1=1#
$db->query("select * from table where username = '$username'");

.net/aspx:

1
2
3
4
5
6
7
8
9
10
string connectionstring = "xxx";
SqlConnection con = new SqlConnection(connectionstring);
con.Open();

string username = Request.QueryString["username"];
string sql = "select * from table where username = '" + username + "'";
SqlDataAdapter adapter = new SqlDataAdapter(sql, con);
DataSet dataSet = new System.Data.DataSet();
adapter.Fill(dataSet);
con.Close()

java:

1
2
3
4
5
6
7
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);

python(flask):

1
2
3
4
5
6
7
@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帮开发者做了封装和对象映射的步骤)
就可以理解这个漏洞:

  • 仅仅抓住输⼊
  • 当数据流⼊侵到控制流时,漏洞就产⽣了
  • “数据流⼊侵控制流”产⽣的⻛险点,在于不同层⾯组件的交汇处(如:代码层与数据库层)

java/MyBatis:
MyBatis需要有⼀个xml配置⽂件来来绑定映射关系

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

简单讲就是MyBatis有两种变量绑定⽅式,分别是:#{}和${}

  • **#**是绑定变量的形式,底层会⽤#{}会被替换为?号,有参数映射,会在
    DefaultParameterHandler中进⾏设置占位符的操作 –>预编译
  • **$**也是绑定变量的形式,{value}是直接被替换为了对应的值,没有参数映射,不会进⾏设置占位
    符的操作 –>拼接

很多代码审计初学者或者很多课程,上来就让去找$符号的原因

python/flask/sqlalchemy:
sqlalchemy是flask最经常配套的orm框架,在许多django项⽬中也常常看到身影。也是⽬前
python上⽤得最⽕的orm框架。

https://blog.csdn.net/weixin_47906106/article/details/123774620
https://blog.csdn.net/weixin_46549605/article/details/123458430
https://blog.csdn.net/qq_41341757/article/details/109462158

这⾥是⼀个有注⼊漏洞的例⼦

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

原因跟上⾯MyBatis类似,依然是⽤户输⼊其实是拼接后才导⼊sqlalchemy层的(不同层⾯组
件的交汇处)

正确的写法:

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

sql注⼊到底在注⼊什么

sql联合查询注⼊
sql堆叠注⼊
sql报错注⼊-N个payload
sql时间盲注
sql布尔盲注
sql带外数据
sql注⼊执⾏命令

核⼼思维

sql注⼊,就是在执⾏⼀段sql语句,关键是数据库类型

想法设法去执⾏⼀条完全的sql语句,把数据带出来或把命令传进去。

产⽣注⼊的输⼊点:
输⼊点决定了我们能⽤什么样的Vector(攻击向量),以及是否需要绕过。

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

每一个可输入点都又可能存在注入

宽字节注⼊

在utf-8还不那么通⽤的时候,各种⽜⻤蛇神都群魔乱舞。各种字符集都存在。(包括现在,如果你去搞⼀个jp、或者kr的站,很可能碰到其他的宽字符集)就造成了⼀种新的注⼊⽅式,叫宽字节注⼊。
概念

我们知道字节是计算机存储世界中最⼩的衡量单位,1Byte = 8bits。所以⼀个字节最⼤能够表示2^8=256个字符。
所以:
对ascii编码⽽⾔,⼀个字符⽤⼀个字节就可以表示,所以ascii编码最多可以表示256个字符。
对GBK编码⽽⾔,⼀个汉字字符需要⽤两个字节表示。所以gbk编码理论上最多可以表示
256*256个字符。

利⽤
因为宽字符的存在,导致⼀些防注⼊的⽅式会被绕过。我们以代码的形式来讲解。

1
2
3
4
$db = init_db();
$db->query(set SET NAMES 'gbk'); //设置gbk字符集
$username = addslashes($_GET['username']); //input: fuck' and 1=1#
$db->query("select * from table where username ='$username'");

我们来考虑下sql语句会是怎么样的呈现,输⼊fuck’ and 1=1#

1
select * from table where username = 'fuck\' and 1=1#'

在这种场景下,我们没办法进⾏sql注⼊,因为我们的单引号被转义了,我们没办法侵⼊到控制流去。

数据在存储的时候⼀定是以字节存储的,但是数据解读的时候,都是以字符的标准去解的。
所以,在连接数据库进⾏sql执⾏(执⾏就是⼀种对存储的解读)时,会按照字符集编码规范去解读,所以遇到了\xDF\x5C的时候,会解读成⼀个字符。

1
2
3
4
5
url输⼊-->php字符串变量-->addslashes-->sql语句-->数据库
fuck%DF' and 1=1# --> fuck\xDF' and 1=1# --> fuck\xDF\' and
1=1# -->
select * from table where username = 'fuck運' and 1=1#' //数据流成功⼊
侵到控制流

所以,代码审计中审阅宽字节注⼊的⽅式,就是查看数据库连接⽂件(⼀般名字类似conn.php),检查其字符集类型是什么,如果不是utf-8(因为脚本的字符集默认是utf-8)就可能有注⼊产⽣。

预编译模式下的注⼊

还是是宽字节注⼊
我们先看例⼦

1
2
3
4
5
6
7
8
9
10
11
12
<?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();
?>

在这种情况下,是⽆法解决宽字符注⼊的问题的。
因为,在

1
2
$stmt = $conn->prepare("select * from table where username =:username");
预编译后变成$stmt->sql = "select * from table where username ='${addslashes($input)}'"

⽆法预编译的输⼊点

like关键字
我们知道sql语句的模糊查找⾥⾯⽤的关键字like,⽽like关键字默认是不会预编译的(如果使⽤Mybatis则是预编译报错)。数据库⽅给出的原因好像是like预编译会造成慢查询和DOS。
只能⼿动去添加预编译。

1
2
3
4
5
6
7
8
9
10
$username=$_GET['username'];
$db="mysql:host=127.0.0.1;dbname=test;";
$dbname="root";
$passwd="root";
$conn=newPDO($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编译报错,然后他们⾃⼰去添加过滤(过滤没写好)或者不过滤(使⽤原⽣语句),导致GG。

与之类似的还有IN关键字,该位置也默认不能预编译,需要在预编译语法中去for循环,有些程序员为了⽅便,可能也会在这边偷⼯减料。

不能加引号的关键字

我们刚才分析了预编译模式下的宽字节注⼊,我们可以发现,预编译+绑定变量的效果,有点类似于做了两个步骤

1
2
$newInput = addslashes($input)   //内容转义
sql = select * from table where column = '$newInput' //强制⽤单引号包裹

那么,结合我们刚才讲的,产⽣注⼊的输⼊点

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

是否有输⼊点是必然不能加单引号的呢,如果不能加单引号,那么就不能预编译于是我们找到了

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

这些地⽅都是不能加单引号的,总结就是

1
表名、列名、limit⼦句、order by[desc/asc`]

跟刚才⼀样,可能很多开发会遗漏这个点,导致存在注⼊。

Mssql提权
https://tttang.com/archive/1545/