然后调用buffer.toString()方法,将buffer解码为utf的字符串。由于待解码的字符长度为1025,所以前1024个字节会在Utf8DecoderBase::Reset中解码出512个字符(216个表情)到buffer_中,剩余的一个buffer 0xf0被传入到Utf8DecoderBase::WriteUtf16Slow中继续解码。
void Utf8DecoderBase::WriteUtf16Slow(const uint8_t* stream, uint16_t* data, unsigned data_length);stream为待解码的buffer,data存储解码后的字符,datalength表示待解码的字符数。此时buffer缓冲区中的512个字符已被copy到data中。
剩余的最后一个buffer 0xf0交给Utf8DecoderBase::WriteUtf16Slow处理,通过调用Utf8::ValueOf进行解码。
最后一个字节的二进制为(0xf0).toString(2)='11110000',根据utf8编码规则,是一个占用4字节的utf8字符的起始字节,于是继续调用Utf8::CalculateValue读取后面的字符。
由于之前完整的buffer被截掉了3个字节,所以理想情况下再次读取下一个字节时读到0x00, 二进制为(0x00).toString(2)='00000000'。很明显,不符合utf8规则预期的字节10xxxxxx,函数返回kBadChar(0xFFFD)。至此整个解码结束,程序无crash。
终于crash上面说到了理想情况,但实际中由于V8引擎的内存管理策略,读完最后一个buffer再继续读取下一个字节时很可能会读到脏数据(根据我打印的log发现读取到脏数据的概率非常高,log详情), 如果继续读取到脏数据刚好和最后一个字节组合一起满足utf8编码规则(这个概率也很高),此时便读取到了一个合法的utf8字符(two characters),而理想情况应该读取到的是kBadChar(one character),那这又会产生什么问题呢?
我们再回到Utf8DecoderBase::WriteUtf16Slow的调用上
void Utf8DecoderBase::WriteUtf16Slow(const uint8_t* stream, uint16_t* data, unsigned data_length) { while (data_length != 0) { unsigned cursor = 0; uint32_t character = Utf8::ValueOf(stream, Utf8::kMaxEncodedSize, &cursor); // There's a total lack of bounds checking for stream // as it was already done in Reset. stream += cursor; if (character > unibrow::Utf16::kMaxNonSurrogateCharCode) { *data++ = Utf16::LeadSurrogate(character); *data++ = Utf16::TrailSurrogate(character); DCHECK(data_length > 1); data_length -= 2; } else { *data++ = character; data_length -= 1; } } }此时data_length=1, 调用uint32_t character = Utf8::ValueOf(stream, Utf8::kMaxEncodedSize, &cursor),读取到满足编码规则的脏数据后if条件满足,于是执行DCHECK(data_length > 1),而此时data_length=1,断言失败,进程退出( 但在我的mac系统上并没有因为断言失败退出,此时继续执行data_length-=2, data_length=-1,while循环无法退出,产生bus error进程crash)。
define DCHECK(condition) do { \ if (!(condition)) { \ V8_Fatal(__FILE__, __LINE__, "CHECK(%s) failed", #condition); \ } \ } while (0) 设计攻击方案了解漏洞原理后,设计一个攻击方案就简单很多了,只要有涉及到buffer操作的地方都可以产生攻击,web开发中常见的就是服务器攻击了,下面我们利用这个漏洞设计一个服务器的攻击方案,导致被攻击服务器进程crash,无法正常提高服务。
web开发中经常会有post请求,而node服务器接收post请求时发生到服务器的数据,必然会使用到buffer,所以主要方案就是向node服务器不断的post恶意构造的buffer。
server使用原生http模块启动一个可以接收post数据的服务器
var http = require('http'); http.createServer(function(req, res){ if(req.method == 'POST') { var buf = [], len = 0; req.on('data', function(chunk){ buf.push(chunk); len += chunk.length; }); req.on('end', function(){ var str = Buffer.concat(buf,len).toString(); res.end(str); }); }else { res.end('node'); } }).listen(3000); client