文首发于看雪论坛:http://bbs.pediy.com/thread-216592.htm
这篇文章将会探索一下CVE-2016-9066,这是 Firefox 中一个简单却有趣(从实际操作的角度来看)的漏洞,可以利用该漏洞来获取代码执行权限。
一段负责加载脚本标签的代码中的一个整数溢出导致了对 mmap 结束块的越界写操作。一种利用方法是在缓冲区后放置一个 JavaScript 堆以便便溢出到它的元数据中来创建一个假的未使用堆单元。然后可以将一个ArrayBuffer实例放置在另一个ArrayBuffer 的内联数据中。ArrayBuffer 对象内部可以被任意修改,就产生了任意读/写原语。至此,实现代码执行就变的非常简单了。完整的 exploit针对 macOS 10.11.6 平台上的 Firefox 48.0.1 进行了测试。
漏洞
以下代码用来加载外部脚本标签:
result
当服务器中的新数据到达时这段代码将被 OnIncrementalData 调用。这是一个简单的整数溢出 Bug,当服务器发送超过 4GB 数据的时候即可发生。数据超过4GB的情况下,capacity 将会环回(wrap around),接下来的对 mBuffer.reserve 函数的调用并不会修改缓冲区。接下来 mDecode->Convert 函数将会把数据写入到8GB缓冲区的尾部(数据在浏览器中以 char16_t 的形式存储),这部分内存将会通过 mmap 块备份(对于一个非常大的块这是通用做法)。
修补也是相当简单:
int32_t haveRead = mBuffer.length();
这个漏洞第一眼看上不并没有什么搞头。它有一个必要条件,需要发送和申请多大几个GB的数据。正如我们即将看到的,该漏洞在我的 2015 MacBook Pro 上相当可靠的被利用,完成整个利用代码只需打开页面用时不到1分钟。我们接下来先来探索一下此漏洞为什么会在 macOS 上被利用并弹出一个计算器,然后我们来改进一下利用代码让它变的更可靠一些,并且占用更低的带宽(剧透:我们将会使用 HTTP 压缩)
操作
当超过 mmap 区的溢出发生时,我们首先关注的是有没有可能在溢出的内存之后可靠地申请一块空间。与一些堆分配器相反,mmap(可以看作是内核提供的内存分配器)是非常具有确定性的:如果没有合适的内存块,调用 mmap 两次将会导致两次连续的内存映射。你可以用下边的代码来尝试一下。注意,实验的结果的异同取决于代码是运行在Linux系统还是 macOS 系统上。mmap 内存区相较于 macOS 系统上由低向高增长,在 Linux 系统上,是由高向低增长。在本篇文章接下来的部分,我们将会专注于 macOS。Linux 或者 Windows 上应该也可能存在相似的利用代码。
#include #include const size_t MAP_SIZE = 0x100000; // 1 MB
上边的程序向我们展示了通过简单的映射所有的内存页直到所有已经存在的分页被填满,然后通过 mmap 再申请一块内存块。为了验证这个过程,我们接下来将会这样做:
加载一个包含一段脚本(将会触发溢出的payload.js)的 HTML 文档并且异步执行一些 JavaScript 代码(code.js,实现第3步和第5步)。
当浏览器请求 payload.js 时,使服务器返回一个 Content-Length 为 0x100000001 但是只发送 0xffffffff 字节的数据。
接下来,让 JavaScript 代码申请多个巨大(1GB)的 ArrayBuffers(在缓冲区实际写入前,内存不一定被使用)。
让服务器发送 payload.js 剩下的 2 个字节。
检查所有 ArrayBuffer 对象的前几个字节,其中的某一个应该会包含服务器发送的数据。
为了实现这个过程,我们需要一些浏览器中的 JavaScript 代码和服务器之间的同步原语。为此,我在 python 的 asyncio 库智商写了一个小小的 webserver,它包含一个方便的 Event 对象,用来和协同程序同步。创建两个全局事件可使客户端代码完成当前任务等待 webserver 进行下一步的操作时通知服务器。/sync 的处理例程如下所示:
async def sync(request, response):
客户端中我是用了同步 XMLHttpRequests 来阻塞脚本的执行,直到服务器完成相关工作:
function synchronize() {
这样,我们就可以实现上边的场景并且将会看到实际上有一个 ArrayBuffer 对象的开始处包含了我们的 payload 字节。不过还有一个小小的限制条件:我们只能通过有效的 UTF-16 来进行溢出,因为这是 Firefox 内部使用的。我们必须记住这一点。现在剩下的就是找到一些更有趣的事情,用内存分配来取代对 ArrayBuffer 的溢出。
寻找目标对象
因为 malloc(同样的 C++ 中的 new 操作)将在某些时候使用 mmap 请求更多的内存,所以像这些操作分配的内存可能是我们代码所感兴趣的。我走了一条不同的路线。最初我想检测一下是否有可能溢出到 JavaScript 对象中,比如说使数组的或者其他类似对象的长度腐败。为此,我开始围绕着 JavaScript 分配器深入发掘,来看 JSObject 被存储在哪里。Spidermonkey(Firefox 中的 JavaScript 引擎)把 JSObjet 存储在两个独立的区域中:
永久堆(The tenured heap)。长生命周期的对象,同时少数被选择的对象类型在此处分配。这是一个相当经典的堆,跟踪自由内存然后在未来重复使用。
托管区。这是一块包含短暂生命周期对象的内存区域。大多数的JSObject最初都被分配在此处,然后如果在下一个垃圾回收周期( 包括更新所有指向他们的指针,因此要求牢记收集器知道其对象的所有指针)中依然存在则被移动到永久堆中。托管区不需要释放列表或者相似的结构:在一个垃圾回收循环之后,托管区只是在所有的存活对象被移出之后简单的声明一下自己是可用的。
永久堆中存储对象的容器叫做 Arenas:
/*
注释已经给出了非常好的总结:Arenas只是简单容器对象,其中分配了相同大小的 JavaScript 对象。它们位于容器对象内,这个块结构本身就是直接使用 mmap 来分配的。在Arena类中有趣的部分是它的 firstFreeSpan 成员:它是 Arena 对象(因此处于一个映射区的开始处)的第一个成员,并且本质上它指明了 Arena 中第一个未使用区块的索引。下边就是FreeSpan的大致结构:
class FreeSpan
其中的 first 和 last 在 Arena 中都是按字节索引,用来指明未使用区块链的头部。那么这就开辟了一条有趣的道路来利用这个漏洞:通过对 Arena 中 firstFreeSpan 成员对象的溢出,我们有可能在另一个对象中分配一个对象,最好是在某些可访问的内联数据中分配。那么接下来我们就能任意的修改内部分配的对象。
这个技巧有一下几点好处:
我们将会看到能在 Arena 中使用指定的偏移分配一个 JavaScript 对象,这就产生了一条内存读写原语。
我们只需溢出接下来的区块的4字节,因此不会破坏任何的指针或是其他的敏感数据。
Arenas/Chunks 可以仅仅通过申请大量的 JavaScript 对象来可靠的产生。
事实证明,ArrayBuffer 对象中高达96字节的数组会被内联存储在该对象头部之后后。这将会跳过托管过程并且因此它将会在 Arena 中被分配。这使得它们成为我们漏洞利用的理想选择。我们会这样做:
申请大量存储 96 字节的 ArrayBuffer 对象。
溢出,并且在我们的缓冲区之后,在 Arena 内部创建一个假的未使用内存块。
申请更多的同样大小的 ArrayBuffer 对象,看是否其中的某一个会被放置在另一个 ArrayBuffer 的数据中(通过扫描所有先前的 ArrayBuffer 中的不为空的内容即可)
垃圾回收的要求
不幸的是,这并不是那样简单:为了让 Spidermonkey 在我们的目标 Arena(被破坏)中申请一个对象,那么这个 Arena 就必须在之前就被(部分)标记为可使用。这意味着,我们需要释放所有的 Arena 中至少一个存储块。我们可以通过删除每第 25个 ArrayBuffer(每个 Arena 有 25 个)来实现,然后强制进行垃圾回收。
Spidermonkey 因为各种各样的原因而触发垃圾回收。诸多方法中似乎使用TOO_MUCH_MALLOC 来触发是最简单的一种:只要通过 malloc 分配了一定数量的字节,它就会被简单的触发。因此,下边的代码足以用来触发垃圾回收:
function gc() {
在此之后,我们的目标 Arena 将会被放置在一个未使用标记链表中,随后的覆盖将会破坏该链表。下一次从被破坏的 Arena 中产生的分配将会返回一个假的处于一个 ArrayBuffer 对象的内联数据中内存块。
(可选阅读)压缩GC
实际上,这有点复杂了。存在一种叫做压缩 GC 模式的垃圾回收,这种模式将会把多个部分填充的 Arena移动去填满另一个 Arena。此举减少了内部碎片,并且协助释放整个内存区域以便系统回收。不管怎样,对于我们来讲,压缩 GC 着实是个麻烦,因为,它有可能填充了我们之前创建的目标Arena。以下的代码用来决定是否应该运行一个压缩 GC:
bool
查看一下代码,应该有方法来阻止压缩 GC 来运行(比如说,展示一些动画)。看来我们很幸运:上文中提到的gc函数(译注:用来产生 ArrayBuffer 的 JS 代码片段)将会在 Spidermonkey 中触发下边的代码流程,因此,阻止压缩 GC 的调用形式将会从 GC_SHRINK 变为 GC_NORMAL。
bool
编写 Exploit
此刻,我们已经拼接了所有的碎片,可以实际动手写利用代码了。一旦我们创建了一个假的自由存储块并在其中创建一个 ArrayBuffer 对象,就能看见其中一个之前申请的 ArrayBuffer 中包含了我们的数据。ArrayBuffer 对象的结构大致如下:
// From JSObject
常量 XXX_SLOT 确定对象的起始位置相应值的偏移量。这样一来,数据指针(DATA_SLOT)将会被存储在 addrof(ArrayBuffer) + sizeof(ArrayBuffer)。
现在,我们就可以构建以下的代码利用原语:
读取绝对内存地址:设置 DATA_SLOT 为所需的地址,然后从 ArrayBuffer 对象中读取。
写入绝对内存地址:和上边一样,不过此刻变成了写操作。
获取 JavaScript 对象的地址:为此,需要设置需要获取地址的对象为 ArrayBuffer 的内部属性,然后通过之前的读内存原语从 slots_指针处读取地址。
进程持续
为了避免浏览器进程在下一次垃圾回收中崩溃,我们必须修复一下几点:
在我们的利用代码中的 ArrayBuffer 之外的 ArrayBuffer,将会被代码内的 ArrayBuffer 数据破坏。可以通过简单的拷贝另一个 ArrayBuffer 对象来覆盖被破坏的数据即可修复。
我们原本在 Arena 中释放的区块此刻看起来应该是一个正在被使用的状态,同样回收器也是这样认为,这将会导致崩溃,因为这块数据已经被其他的数据(比如,FreeSpan 实例)覆盖了。我们可以通过重新载入我们 Arena 中原始 firstFreeSpan 域的值来标记该块为可使用状态。
以上几点就足以保证浏览器在跑完利用代码之后仍然存活了。
概要
插入一个脚本标签加载 payload,最终触发漏洞
等待服务器发送 2GB+1 字节的数据。浏览器此刻应该已经申请了我们最终要溢出的内存块。像我们最初的最简单的 POC 那样尝试使用 ArrayBuffer 对象来填充 mmap 中的区块。
申请包含大小为 96 字节(数据存储空间分配在对象之后的最大大小)的JavaScript Arenas(内存区域),接下来期待其中之一被放置在了我们将要溢出的缓冲区之后。由于 mmap 分配连续的区域,所以如果我们不申请足够的空间或者有什么其它的东西已经被分配在此处则可能会导致失败。
让服务器尽情的发送 0xffffffff 字节的数据,完全填充当前块。
释放所有的 Arena 中的一个 ArrayBuffer,然后尝试触发垃圾回收,使 Arena 被插入到未使用块链表中。
使服务器发送剩下的数据。这将会触发溢出并破坏一个 Arenas 内部的一个未使用内存块链表。链表被改写使得其中之一的 ArrayBuffer 中第一个包含内联数据的自由块被包含在 Arena 中。
申请更多的 ArrayBuffer。如果一切正常,其中之一的 ArrayBuffer 将会被分配在另一个 ArrayBuffer 内联数据内部。找到那个 ArrayBuffer!。
如果找到了,构造一个任意内存读写原语。现在我们就可以修改 ArrayBuffer 内部的数据指针,所以这是非常容易做到的。
修复被破坏的对象,以便使我们的利用代码跑完之后浏览器进程依然存活。
弹出计算器
剩下的工作就是以某种方式弹出一个计算器。
一个简单的跑自己代码的方式是嘿咻 JIT了,但是,这个技术(部分)在Firefox 中被削弱了。考虑到我们开发的原语,可以绕过被削弱的部分(比如,使用 ROP 来转移控制),但对于简单的 PoC 来说,似乎有点复杂。
有一些其他的 Firefox 相关的技巧来通过滥用特权的 JavaScript 获取代码执行权限,但是这些需要对浏览器状态进行不必要的修改(比如,关闭所有安全性,以便病毒可以接管这台电脑)。
我最终使用了一些标准的 CTF 技巧来完成利用代码:寻找对 libc 中第一个参数为字符串的函数的交叉引用(此例中,选用strcmp),我发现Date.toLocalFormat的执行并且注意到了该函数将第一个参数从JSString转换为C-String,它的第一个参数被用来做strcmp。因此,我们可以简单的 strcmp 的 GOT 换成 system,然后执行data_obj.toLocaleFormat("open -a /Applications/Calculator.app");。搞定:).
改进Exploit
这时,基本的利用代码已经完成了, 接下来我们将会描述如何使它变的更可靠一些以及占用更少的带宽。
增强鲁棒性
此时此刻我们的利用代码知识申请了一些非常大的 ArrayBuffer 实例(每个1GB)用来填充 mmap 空间,然后再分配一大堆 js :: Arena 实例用来溢出。因此,该操作假设浏览器的堆操作在利用期间多多少少是确定的。既然这不一定是这样,我们希望让我们的漏洞更加强大一些。
快速浏览一下 mozilla::Vector 类(用来保留脚本缓冲区)接下来的操作向我们展示了它使用了 realloc 在其需要的时候来倍增自己的空间。由于 jemalloc 直接使用 mmap 来申请较大的区块,这就给了我们以下的分配模式:
mmap 1MB
mmap 2MB, munmap previous chunk
mmap 4MB, munmap previous chunk
mmap 8MB, munmap previous chunk
…
mmap 8GB, munmap previous chunk
因为当前区块的大小总是会大于之前所有区块的大小,这将导致我们的最终缓冲区之前存在大量的可用空间。理论上,我们可以计算空闲空间之和,然后申请一个大的ArrayBuffer。实际上,这行不通,因为当服务器发送数据到来时,在浏览器完成解压缩最后一块数据完成之前有一些其他的申请空间的操作。此外 jemalloc 还保留了一部分被释放的内存以供以后使用。相反,我们会尽快在浏览器中申请被释放的空间,理由如下:
JavaScript代码使用sync来等待服务器
服务器发送到下一个 2 的幂(MB)的所有数据从而在最后触发一次对 realloc 的调用。浏览器此时将会释放一个大小已知的空间。
服务器设置 server_done_event 信号,使 JavaScript 代码继续执行。
JavaScript 代码申请一个与之前空间大小相同的 ArrayBuffer 实例来填充该空间。
重复上述步骤直到我们发送完 0x80000001 字节数据(强制申请最后一块空间)。
这个简单的算法服务器端代码在这里,客户端第一步的代码在这里。使用这个算法,我们可以通过仅喷射几兆的 ArrayBuffer 实例而不是多个千兆字节来相当可靠地获得分配在目标缓冲区之后的空间。
减少网络负载
目前,我们的利用代码需要通过服务器发送4GB的数据。可以简单的通过下述方法来改进:使用HTTP压缩。zlib有一个好处,支持流式压缩,它可以逐步压缩 payload。有了这个,我们只需将 payload 的每个部分添加到在 zlib 流中,然后调用 flush 来获取 payload 的下一个压缩块,并将其发送到服务器(译注:服务器发送到浏览器,笔误?)。服务器(译注:浏览器,笔误?)在收到该块后解压缩该文件,并执行所需要的操作(比如,执行一次 realloc 操作)。
poc.py中的 construct_payload 方法执行了该过程,并且将 payload 的大小减少到大约18MB。
资源使用
至少在理论上,exploit 需要非常大量的内存:
一个保存我们 payload 脚本的 8GB的缓冲区。实际上,更像是12GB,因为在最后的 realloc 期间,有 4GB空间必须得拷贝到一个新的 8GB 空间中。
需要 JavaScript 申请大量的(大约6GB)缓冲区来填充由 realloc 创建的内存块。
大约 256 MB的 ArrayBuffers。
不管怎么说,因为许多的缓冲区并没有被写入,所以不一定得消耗如此多的物理空间。更多的,在最终 realloc 中,只有 4GB新的缓冲区会被写入之前释放的旧的缓冲区中,因此真正需要的仅仅是 8GB 而已。
不过这还是非常的占内存。然而,如果物理内存降低后,还是有一些技术来帮助减少内存占用:
内存压缩(macOS):大量的内存区域可以被压缩然后交换至交换分区。这对于我们的案例来讲是完美的,因为8GB的缓冲区将完全被0填充。这个效果可以在Activity Monitor.app中观察到,在利用代码运行期间的某些时候会显示超过6GB的内存为“压缩”。
页重复数据删除(Windows,Linux):具有相同内容的的内存页会被映射为具有写时复制(COW,copy-on-write)属性的相同物理页面(基本上将内存使用减少到4KB)。
CPU 使用率在峰值期间(解压缩数据)也是相当的高。然而可以通过延迟发送较小的块之间的时间(这显然会增加漏洞利用的时间)来进一步降低 CPU 的压力。这也将给 OS 更多的时间来压缩和或删除大的重复数据内存缓冲区。
进一步可能的改进
目前的漏洞利用有几个不可靠的因素,主要是处理时机:
在发送 payload 数据期间,如果 JavaScript 在浏览器完全处理下一个块之前运行分配,则分配是不同步的。这可能导致利用失败。理想情况下,JavaScript 将在接收和处理下一个块后立即执行分配,这个或许可以通过观察 CPU 使用情况来确定。
如果垃圾回收在我们已经破坏 FreeSpan 之后但在我们修复之前运行,崩溃!
如果在我们释放了一些数组缓冲区之后,在触发了溢出之前,如果一个压缩的垃圾回收循环运行,那么漏洞利用就会失败,因为 Arena 将再次被填满。
如果假的单元块恰好放在释放的 ArrayBuffer 的单元块内,那么我们的漏洞利用将失败,浏览器会在下一个垃圾回收周期中崩溃。每个 Arena 有 25 个单元块,则在理论上有 1/25 失败的几率。然而,在我的实验中,未使用单元块总是位于相同的偏移处(Arena 的 1216 字节处),表明在开发阶段引擎的状态是相当确定的(至少关于 Arena 所持有的 160 字节的对象是来说是这样的)。
从我的经验来看,如果浏览器没有大量的处理任务,这个漏洞利用率非常可靠(>95%)。如果 10 个以上的其他选项卡是打开的,则漏洞利用仍然有效,但如果大型 Web 应用程序正在加载,则可能会失败。
结论
虽然从攻击者的角度来看,这个漏洞并不理想,但它仍然可以相当可靠地在低带宽下利用。过程中有趣的是使用的各种技术(压缩,页重复删除。。。)来使得这个漏洞更加简单的被利用。
考虑到如何防止这种错误的可利用性,有几点是我想说明的。一个相当通用的缓解措施是使用保护页面(无论用什么来访问保护页面都会产生段错误)。保护页面必须在每个mmap分配区域之前或之后分配,并且此举将防止对这种线性溢出的利用。但是,它们不会防止非线性溢出,比如说这个漏洞。另一种可能性是引入内部mmap随机化来分散整个地址空间中的分配区域(可能仅在64位系统上有效)。这最好由内核执行,当然也可以在用户空间中完成。
本文由 看雪翻译小组 zplusplus 编译,来源root@saelo
看雪论坛:http://bbs.pediy.com/
微信公众号 ID:ikanxue
微博:看雪安全
投稿、合作:www.kanxue.com
们先来看个例子,这个容器里有两段文字和一张图片,由于容器的高度小于包含内容的总高度,不能显示全部的内容。此时,我们可以通过鼠标滚轮使容器的内容上下滚动,来查看全貌。
这个效果是如何实现的呢?你一定想到的是 iframe!实际上,通过CSS的溢出样式也能实现这个功能。
CSS 的 overflow 属性,指定了当一个元素的内容太大,无法容纳在指定区域时,是否要裁剪内容或添加滚动条。overflow 属性有以下值:
visible - 默认。溢出的内容没有被剪掉。内容会在元素的框外呈现。
hidden - 溢出的内容被剪切,其余的内容将不可见。
scroll - 溢出的内容被剪掉了,并且增加了一个滚动条来查看其余的内容。
auto - 类似于 scroll,但它只在必要时添加滚动条。
这是我已经制作好的一个页面。div 容器里包含三个段落,分别放置的是两段文本和一张图片。容器设置了背景颜色、内填充和边框。
在浏览器里预览,内容撑满整个容器,全部显示出来了。
接下来给外部的 div 容器添加 width: 50%、height: 300px 的样式。
HTML
<div id="overflowTest">
<p>
国外某公司呢开发了一款自动找bug的AI程序,但是这个程序“悟道”了!AI“悟道”后解决bug的终极方案就是:没有代码就没有bug,全删!这是一个发生在美国的真实事件,美国版的大众点评,本来想训练AI来消除bug,结果它把所有的内容全部删除了。来看看 软件的更新介绍:
</p>
<p>
<img src="./image.jpeg" alt="" width="600">
</p>
<p>
大概意思就是:我们为本周使用该app遇到问题的用户致歉,我们训练了一个神经网络,来消愁该app的漏洞,但它删除了一切。还好这事发生在国外,要是在国内的互联网公司,这么大的安全事故,又要有一个工程师“祭天”了。
</p>
</div>
CSS
#overflowTest {
background: #4CAF50;
padding: 15px;
border: 1px solid #ccc;
}
我们看,容器的高度虽然小于内容的总体高度,但是容器里的内容还是全部显示了。这说明浏览器在默认情况下, 对容器溢出的部分是完全显示的。
回到 css 代码,给 div 容器再添加样式 overflow: visible。
效果是一样的,说明 visible 是 overflow 属性的默认值。
如果希望把溢出的部分隐藏,需要将 overflow 的值设置为 hidden(注意这个单词读 [ˈhɪdn])。
果然,溢出的部分隐藏了,此时是无法看到隐藏的内容的。
如果希望通过滚动条查看隐藏的内容,需要将 overflow 的值设置为 scroll。
这样,就可以通过滚动条来查看全部内容了!
当然,也可以把 overflow 的值设置为 auto,此时和 scroll 效果一样。
回到 css 代码,我们将 div 容器的 width 修改为 30%。
看效果,水平和垂直方向都可以滚动了。
如果只希望某一个方向滚动,可以采用 overflow-x 和 overflow-y 两个样式属性,它们指定是否只在水平方向或垂直方向上滚动。
比如,注释掉 overflow: scroll ,添加样式 overflow-y: scroll。
我们看,此时垂直方向可以滚动,可是水平方向也可以滚动,说明 overflow-y 只能约束垂直方向。
再添加样式 overflow-x: hidden。
这回,水平方向就不能滚动了,只有垂直方向可以滚动!
有时,文字标题或文字内容所在的容器空间有限,不能显示全部内容的时候,会出现一个省略号,示意读者:内容显示不全,可以点击查看详细内容。这个效果如何实现呢?
在 body 里添加一个 h1 元素,填入一些内容。
在 css 文件里,定义选择器 h1,声明样式 width: 310px,overflow: hidden,white-space: nowrap。让文本在一行上显示,并且溢出的部分隐藏。
很显然,此时的效果用户体验不好——文字被剪切了。
回到 css 代码,给 h1 再添加一个 text-overflow: ellipsis 样式。ei 累铺赛司
我们再看,效果实现了!
篇文章主要给大家介绍一下在html页面中如何让单行文本以及多行文本溢出显示省略号(…)。
当我们在编写网页代码的时候,肯定会遇到过文字列表中的文字太多超出了我们所写的宽度,导致文本换行或者文本超出了界限,这时有人就会说了,让后台限制一下调用的文字个数不就行了吗,但是我们在做响应式的时候由于是百分比布局,无法计算一行会显示多少个文字,所以这并不是一个好的解决方案,我们使用css3就可以轻松的实现,而且简单好用。
核心css语句:
1、overflow:hidden; (顾名思义超出限定的宽度就隐藏内容)
2、white-space: nowrap; (设置文字在一行显示不能换行)
3、text-overflow: ellipsis;(规定当文本溢出时显示省略符号来代表被修剪的文本)
我们具体的代码效果演示就如下图所示:(设置ul宽度为300,超出的文字内容让其自动隐藏并显示...)
我们在编写网页代码时,有时候新闻列表页中新闻简介可能有一行或者多行,我们要如何处理这种问题,让其超出多行后还能显示省略号呢,不要慌,我们css还是很强大的,已经给我们提供了方法来处理这种问题了。
核心css语句:
1、-webkit-line-clamp:2; (用来限制在一个块元素显示的文本的行数,2表示最多显示2行。 为了实现该效果,它需要组合其他的WebKit属性)
2、display: -webkit-box; (和1结合使用,将对象作为弹性伸缩盒子模型显示 )
3、-webkit-box-orient:vertical;( 和1结合使用 ,设置或检索伸缩盒对象的子元素的排列方式 。)
4、overflow:hidden; (顾名思义超出限定的宽度就隐藏内容)
5、text-overflow: ellipsis;(规定当文本溢出时显示省略符号来代表被修剪的文本)
我们具体的代码效果演示就如下图所示:(设置段落p宽度为300,超出2行的文字内容让其自动隐藏并显示...)
好了,本篇文章就给大家说到这里,大家自己下来可以自己找例子写一下试一试到底能不能实现我们所说的效果,以后在写页面的的遇到这种问题的时候直接复制使用即可。
每日金句:必须从过去的错误学习教训而非依赖过去的成功。喜欢我的文章的小伙伴记得关注一下哦,每天将为你更新最新知识。
*请认真填写需求信息,我们会在24小时内与您取得联系。