我用Java写了一个WebSocket帧解码器:
private byte[] decodeFrame(byte[] _rawIn) {
int maskIndex = 2;
byte[] maskBytes = new byte[4];
if ((_rawIn[1] & (byte) 127) == 126) {
maskIndex = 4;
} else if ((_rawIn[1] & (byte) 127) == 127) {
maskIndex = 10;
}
System.arraycopy(_rawIn, maskIndex, maskBytes, 0, 4);
byte[] message = new byte[_rawIn.length - maskIndex - 4];
for (int i = maskIndex + 4; i < _rawIn.length; i++) {
message[i - maskIndex - 4] = (byte) (_rawIn[i] ^ maskBytes[(i - maskIndex - 4) % 4]);
}
return message;
}
它有效,但我不知道如何验证帧以确保它只解码有效帧。
遗憾的是,协议说明http://tools.ietf.org/html/rfc6455
并没有说明帧验证。
答案 0 :(得分:15)
解析原始的websocket框架很容易。 但是你必须一次检查一个字节的标题。
这是一个粗略的例子:
我留下了一些TODO给你自己解决(当然,在阅读了RFC-6455规范之后)
您可以验证的事项:
Base Framing Protocol: RFC-6455 - Section 5.2
Client-to-Server Masking: RFC 6455 - Section 5.3
Fragmentation: RFC 6455 - Section 5.4
Control Frames: RFC 6455 - Section 5.5
Close Frames: RFC 6455 - Section 5.5.1
Data Frames: RFC 6455 - Section 5.6
虽然您可以在单个帧级别进行验证,但您会发现上面的一些验证是对多个帧之间的状态和行为的验证。您可以在Sending and Receiving Data: RFC 6455 - Section 6中找到更多此类验证。
但是,如果混合中有扩展,那么您还需要从协商扩展堆栈的角度处理帧。 上面的一些测试在使用扩展时似乎无效。
示例:您正在使用Compression Extension (RFC-7692)(例如permessage-deflate
),然后无法使用网络上的原始帧验证TEXT有效内容,因为您必须首先通过延期。请注意,扩展可以更改碎片以满足其需要,这可能会使您的验证陷入困境。
package websocket;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
public class RawParse
{
public static class Frame
{
byte opcode;
boolean fin;
byte payload[];
}
public static Frame parse(byte raw[])
{
// easier to do this via ByteBuffer
ByteBuffer buf = ByteBuffer.wrap(raw);
// Fin + RSV + OpCode byte
Frame frame = new Frame();
byte b = buf.get();
frame.fin = ((b & 0x80) != 0);
boolean rsv1 = ((b & 0x40) != 0);
boolean rsv2 = ((b & 0x20) != 0);
boolean rsv3 = ((b & 0x10) != 0);
frame.opcode = (byte)(b & 0x0F);
// TODO: add control frame fin validation here
// TODO: add frame RSV validation here
// Masked + Payload Length
b = buf.get();
boolean masked = ((b & 0x80) != 0);
int payloadLength = (byte)(0x7F & b);
int byteCount = 0;
if (payloadLength == 0x7F)
{
// 8 byte extended payload length
byteCount = 8;
}
else if (payloadLength == 0x7E)
{
// 2 bytes extended payload length
byteCount = 2;
}
// Decode Payload Length
while (--byteCount > 0)
{
b = buf.get();
payloadLength |= (b & 0xFF) << (8 * byteCount);
}
// TODO: add control frame payload length validation here
byte maskingKey[] = null;
if (masked)
{
// Masking Key
maskingKey = new byte[4];
buf.get(maskingKey,0,4);
}
// TODO: add masked + maskingkey validation here
// Payload itself
frame.payload = new byte[payloadLength];
buf.get(frame.payload,0,payloadLength);
// Demask (if needed)
if (masked)
{
for (int i = 0; i < frame.payload.length; i++)
{
frame.payload[i] ^= maskingKey[i % 4];
}
}
return frame;
}
public static void main(String[] args)
{
Charset UTF8 = Charset.forName("UTF-8");
Frame closeFrame = parse(hexToByteArray("8800"));
System.out.printf("closeFrame.opcode = %d%n",closeFrame.opcode);
System.out.printf("closeFrame.payload.length = %d%n",closeFrame.payload.length);
// Examples from http://tools.ietf.org/html/rfc6455#section-5.7
Frame unmaskedTextFrame = parse(hexToByteArray("810548656c6c6f"));
System.out.printf("unmaskedTextFrame.opcode = %d%n",unmaskedTextFrame.opcode);
System.out.printf("unmaskedTextFrame.payload.length = %d%n",unmaskedTextFrame.payload.length);
System.out.printf("unmaskedTextFrame.payload = \"%s\"%n",new String(unmaskedTextFrame.payload,UTF8));
Frame maskedTextFrame = parse(hexToByteArray("818537fa213d7f9f4d5158"));
System.out.printf("maskedTextFrame.opcode = %d%n",maskedTextFrame.opcode);
System.out.printf("maskedTextFrame.payload.length = %d%n",maskedTextFrame.payload.length);
System.out.printf("maskedTextFrame.payload = \"%s\"%n",new String(maskedTextFrame.payload,UTF8));
}
public static byte[] hexToByteArray(String hstr)
{
if ((hstr.length() < 0) || ((hstr.length() % 2) != 0))
{
throw new IllegalArgumentException(String.format("Invalid string length of <%d>",hstr.length()));
}
int size = hstr.length() / 2;
byte buf[] = new byte[size];
byte hex;
int len = hstr.length();
int idx = (int)Math.floor(((size * 2) - (double)len) / 2);
for (int i = 0; i < len; i++)
{
hex = 0;
if (i >= 0)
{
hex = (byte)(Character.digit(hstr.charAt(i),16) << 4);
}
i++;
hex += (byte)(Character.digit(hstr.charAt(i),16));
buf[idx] = hex;
idx++;
}
return buf;
}
}
答案 1 :(得分:0)
websocket协议不包含任何类型的校验和,如果这是您正在寻找的内容。如果数据框中出现错误,您知道的唯一方法是因为数据出错或者因为后续帧出现而且有趣#34; (意外操作码,比预期更长或更短等)。
答案 2 :(得分:0)
针对连接到websocket服务器的应用程序的第一个安全措施是HTTP websocket握手。当它不包含Upgrade: websocket
,Sec-WebSocket-Key
或Sec-WebSocket-Version: 13
时,它甚至不是RFC6455 websocket客户端,必须被拒绝。
第二个安全措施适用于说出websocket但专为不同应用程序设计的客户。这是Sec-WebSocket-Protocol: something
标题。此标头是可选的,但应该是一个标识客户端要使用的应用程序的字符串。当值与服务器期望的应用程序不匹配时,应拒绝客户端。
对于认为他们说websocket并连接到正确的服务器但实际上在websocket协议实现中存在错误的客户端的最后一个保存是保留位。
屏蔽密钥或长度没有非法值。在解释了与有效载荷不足或太多的数据之后,错误的长度将导致下一帧开始,但这可能难以检测。发生这种情况的唯一迹象是,所谓的框架的第一个字节没有意义。
帧的第2位,第3位和第4位是保留的,根据RFC“必须为0,除非协商扩展[...]如果收到非零值[...]接收端点必须使WebSocket连接失败。“还没有使用这些位的扩展,当有一个扩展时,你将不得不做一些事情来打开它。因此,当其中一个位非零时,就会出现问题。
如果需要,可以在协议级别添加进一步的安全措施,例如每个消息必须开始和/或结束的特定魔法字节值(请记住,有多个片段消息,浏览器可以使用这感觉就像这样)。我目前开发的应用程序使用JSON有效负载,因此当消息不是以{
开头并以}
开头的有效JSON字符串时,我知道客户端已损坏(或我的服务器帧解码方法是,更有可能)。
答案 3 :(得分:-1)
我目前对websockets的兴趣让我可以帮助解决这个问题,虽然我对websockets是全新的。
http://tools.ietf.org/html/rfc6455#section-5.2提供了数据框的高级视图。您将测试第一个字节的最后四个,因此raw_in [0]&lt;&lt;&lt;&lt; 4。这将给你最后四个我对位操作不太好所以我不知道如何得到最后4位代表0000 1111-0000 0000 vs 1111 0000-0000 0000.所以你可以看到0001 op代码是文本框架,0010 op代码是二进制框架等等。因此,如果您只想除文本框架外,只需测试第一个字节的最后四位是否为0001。