Add tcp syn packet fingerprinting to redbean

This change also fixes bugs in enoprotoopt reporting with setsockopt and
getsockopt error returns.
This commit is contained in:
Justine Tunney 2022-07-17 02:40:39 -07:00
parent 866b21a151
commit 4d25f8c3c9
75 changed files with 1551 additions and 115 deletions

View file

@ -20,8 +20,20 @@ db:exec[[
INSERT INTO test (content) VALUES ('Hello Sqlite3');
]]
function OnServerListen(fd, ip, port)
unix.setsockopt(fd, unix.SOL_TCP, unix.TCP_SAVE_SYN, true)
return false
end
function OnClientConnection(ip, port, serverip, serverport)
syn, synerr = unix.getsockopt(GetClientFd(), unix.SOL_TCP, unix.TCP_SAVED_SYN)
end
-- this intercepts all requests if it's defined
function OnHttpRequest()
Log(kLogInfo, "client is running %s and reports %s" % {
finger.GetSynFingerOs(finger.FingerSyn(syn)),
GetHeader('User-Agent')})
if HasParam('magic') then
Write('<p>\r\n')
Write('OnHttpRequest() has intercepted your request<br>\r\n')

65
tool/net/demo/finger.lua Normal file
View file

@ -0,0 +1,65 @@
-- fingerprinting example
Write[[<!doctype html>
<title>redbean binary trees</title>
<style>
body { padding: 1em; }
h1 a { color: inherit; text-decoration: none; }
h1 img { border: none; vertical-align: middle; }
input { margin: 1em; padding: .5em; }
p { word-break: break-word; max-width: 650px; }
dt { font-family: monospace; }
dd { margin-top: 1em; margin-bottom: 1em; }
.hdr { text-indent: -1em; padding-left: 1em; }
</style>
<h1>
<a href="/"><img src="/redbean.png"></a>
<a href="finger.lua">redbean finger demo</a>
</h1>
]]
Write[[
<h2>Your TCP SYN Packet</h2>
]]
-- See .init.lua hooks which set SYN and SYNERR globals.
if syn then
if syn ~= '' then
Write('<dl>\r\n')
Write('<dt>syn = unix.getsockopt(GetClientFd(), unix.SOL_TCP, unix.TCP_SAVED_SYN)\r\n')
Write('<dd>')
Write(EscapeHtml(EncodeLua(syn)))
Write('\r\n')
Write('<dt>finger.FingerSyn(syn)\r\n')
Write('<dd>')
Write(EscapeHtml(EncodeLua(finger.FingerSyn(syn))))
Write('\r\n')
Write('<dt>finger.DescribeSyn(syn)\r\n')
Write('<dd>')
Write(EscapeHtml(EncodeLua(finger.DescribeSyn(syn))))
Write('\r\n')
Write('<dt>finger.GetSynFingerOs(finger.FingerSyn(syn))\r\n')
Write('<dd>')
Write(EscapeHtml(EncodeLua(finger.GetSynFingerOs(finger.FingerSyn(syn)))))
Write('\r\n')
Write('</dl>\r\n')
else
Write([[
<p>
your operating system returned an empty string as the syn
packet! did you remember to use setsockopt(TCP_SAVE_SYN)?
did you call getsockopt(TCP_SAVED_SYN) more than once?
</p>
]])
end
else
Write([[
<p>
your operating system most likely doesn't support TCP_SAVED_SYN
because getsockopt() returned %s.
</p>
]] % {EscapeHtml(tostring(synerr))})
end

View file

@ -1974,6 +1974,100 @@ MAXMIND MODULE
For further details, please see maxmind.lua in redbean-demo.com.
────────────────────────────────────────────────────────────────────────────────
FINGER MODULE
This is an experimental module that, like the maxmind module, gives
you insight into what kind of device is connecting to your redbean.
This module can help you protect your redbean because it provides
tools for identifying clients that misrepresent themselves. For
example the User-Agent header might report itself as a Windows
computer when the SYN packet says it's a Linux computer.
function OnServerListen(fd, ip, port)
unix.setsockopt(fd, unix.SOL_TCP, unix.TCP_SAVE_SYN, true)
return false
end
function OnClientConnection(ip, port, serverip, serverport)
fd = GetClientFd()
syn = unix.getsockopt(fd, unix.SOL_TCP, unix.TCP_SAVED_SYN)
end
function OnHttpRequest()
Log(kLogInfo, "client is running %s and reports %s" % {
finger.GetSynFingerOs(finger.FingerSyn(syn)),
GetHeader('User-Agent')})
Route()
end
The following functions are provided.
finger.FingerSyn(syn_packet_bytes:str)
├─→ synfinger:uint32
└─→ nil, error:str
Fingerprints IP+TCP SYN packet.
This returns a hash-like magic number that reflects the SYN packet
structure, e.g. ordering of options, maximum segment size, etc. We
make no guarantees this hashing algorithm won't change as we learn
more about the optimal way to fingerprint, so be sure to save your
syn packets too if you're using this feature, in case they need to
be rehashed in the future.
This function is nil/error propagating.
finger.GetSynFingerOs(synfinger:uint32)
├─→ osname:str
└─→ nil, error:str
Fingerprints IP+TCP SYN packet.
If `synfinger` is a known hard-coded magic number, then one of the
following strings may be returned:
- `"LINUX"`
- `"WINDOWS"`
- `"XNU"`
- `"NETBSD"`
- `"FREEBSD"`
- `"OPENBSD"`
If this function returns nil, then one thing you can do to help is
file an issue and share with us your SYN packet specimens. The way
we prefer to receive them is in EncodeLua(syn_packet_bytes) format
along with details on the operating system which you must know.
finger.DescribeSyn(syn_packet_bytes:str)
├─→ description:str
└─→ nil, error:str
Describes IP+TCP SYN packet.
The layout looks as follows:
TTL:OPTIONS:WSIZE:MSS
The `TTL`, `WSIZE`, and `MSS` fields are unsigned decimal fields.
The `OPTIONS` field communicates the ordering of the commonly used
subset of tcp options. The following character mappings are defined.
TCP options not on this list will be ignored.
- E: End of Option list
- N: No-Operation
- M: Maxmimum Segment Size
- K: Window Scale
- O: SACK Permitted
- A: SACK
- e: Echo (obsolete)
- r: Echo reply (obsolete)
- T: Timestamps
This function is nil/error propagating.
────────────────────────────────────────────────────────────────────────────────
ARGON2 MODULE
@ -3071,6 +3165,17 @@ UNIX MODULE
`level` and `optname` may be one of the following pairs. The ellipses
type signature above changes depending on which options are used.
`optname` is the option feature magic number. The constants for
these will be set to `0` if the option isn't supported on the host
platform.
Raises `ENOPROTOOPT` if your `level` / `optname` combination isn't
valid, recognized, or supported on the host platform.
Raises `ENOTSOCK` if `fd` is valid but isn't a socket.
Raises `EBADF` if `fd` isn't valid.
unix.getsockopt(fd:int, level:int, optname:int)
├─→ value:int
└─→ nil, unix.Errno
@ -3145,9 +3250,19 @@ UNIX MODULE
close(). Sometimes it's desirable to have extra assurance on errors
happened, even if it comes at the cost of performance.
Returns `EINVAL` if settings other than the above are used.
unix.setsockopt(serverfd:int, unix.SOL_TCP, unix.TCP_SAVE_SYN, enabled:int)
├─→ true
└─→ nil, unix.Errno
unix.getsockopt(clientfd:int, unix.SOL_TCP, unix.TCP_SAVED_SYN)
├─→ syn_packet_bytes:str
└─→ nil, unix.Errno
Returns `ENOSYS` if setting isn't supported by the host OS.
This `TCP_SAVED_SYN` option may be used to retrieve the bytes of the
TCP SYN packet that the client sent when the connection for `fd` was
opened. In order for this to work, `TCP_SAVE_SYN` must have been set
earlier on the listening socket. This is Linux-only. You can use the
`OnServerListen` hook to enable SYN saving in your Redbean. When the
`TCP_SAVE_SYN` option isn't used, this may return empty string.
unix.poll({[fd:int]=events:int, ...}[, timeoutms:int])
├─→ {[fd:int]=revents:int, ...}

98
tool/net/lfinger.c Normal file
View file

@ -0,0 +1,98 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "net/finger/finger.h"
#include "third_party/lua/lauxlib.h"
// finger.DescribeSyn(syn_packet_bytes:str)
// ├─→ description:str
// └─→ nil, error:str
static int LuaDescribeSyn(lua_State *L) {
char *q, *r;
size_t n, m;
const char *p;
luaL_Buffer b;
if (!lua_isnoneornil(L, 1)) {
p = luaL_checklstring(L, 1, &n);
m = 128;
q = luaL_buffinitsize(L, &b, m);
if ((r = DescribeSyn(q, m, p, n))) {
luaL_pushresultsize(&b, r - q);
return 1;
} else {
lua_pushnil(L);
lua_pushstring(L, "bad syn packet");
return 2;
}
} else {
return lua_gettop(L);
}
}
// finger.FingerSyn(syn_packet_bytes:str)
// ├─→ synfinger:uint32
// └─→ nil, error:str
static int LuaFingerSyn(lua_State *L) {
size_t n;
uint32_t x;
const char *p;
if (!lua_isnoneornil(L, 1)) {
p = luaL_checklstring(L, 1, &n);
if ((x = FingerSyn(p, n))) {
lua_pushinteger(L, x);
return 1;
} else {
lua_pushnil(L);
lua_pushstring(L, "bad syn packet");
return 2;
}
} else {
return lua_gettop(L);
}
}
// finger.GetSynFingerOs(synfinger:uint32)
// ├─→ osname:str
// └─→ nil, error:str
static int LuaGetSynFingerOs(lua_State *L) {
int os;
if (!lua_isnoneornil(L, 1)) {
if ((os = GetSynFingerOs(luaL_checkinteger(L, 1)))) {
lua_pushstring(L, GetOsName(os));
return 1;
} else {
lua_pushnil(L);
lua_pushstring(L, "unknown syn os fingerprint");
return 2;
}
} else {
return lua_gettop(L);
}
}
static const luaL_Reg kLuaFinger[] = {
{"DescribeSyn", LuaDescribeSyn}, //
{"FingerSyn", LuaFingerSyn}, //
{"GetSynFingerOs", LuaGetSynFingerOs}, //
{0}, //
};
int LuaFinger(lua_State *L) {
luaL_newlib(L, kLuaFinger);
return 1;
}

11
tool/net/lfinger.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef COSMOPOLITAN_TOOL_NET_LFINGER_H_
#define COSMOPOLITAN_TOOL_NET_LFINGER_H_
#include "third_party/lua/lauxlib.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int LuaFinger(lua_State *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_TOOL_NET_LFINGER_H_ */

View file

@ -54,6 +54,7 @@ TOOL_NET_DIRECTDEPS = \
LIBC_UNICODE \
LIBC_X \
LIBC_ZIPOS \
NET_FINGER \
NET_HTTP \
NET_HTTPS \
THIRD_PARTY_ARGON2 \
@ -95,6 +96,7 @@ o/$(MODE)/tool/net/redbean.com.dbg: \
$(TOOL_NET_DEPS) \
o/$(MODE)/tool/net/redbean.o \
o/$(MODE)/tool/net/lfuncs.o \
o/$(MODE)/tool/net/lfinger.o \
o/$(MODE)/tool/net/lre.o \
o/$(MODE)/tool/net/ljson.o \
o/$(MODE)/tool/net/lmaxmind.o \
@ -178,6 +180,7 @@ o/$(MODE)/tool/net/demo/unix-dir.lua.zip.o \
o/$(MODE)/tool/net/demo/unix-info.lua.zip.o \
o/$(MODE)/tool/net/demo/unix-finger.lua.zip.o \
o/$(MODE)/tool/net/demo/fetch.lua.zip.o \
o/$(MODE)/tool/net/demo/finger.lua.zip.o \
o/$(MODE)/tool/net/demo/call-lua-module.lua.zip.o \
o/$(MODE)/tool/net/demo/store-asset.lua.zip.o \
o/$(MODE)/tool/net/demo/maxmind.lua.zip.o \
@ -214,6 +217,7 @@ o/$(MODE)/tool/net/redbean-demo.com.dbg: \
$(TOOL_NET_DEPS) \
o/$(MODE)/tool/net/redbean.o \
o/$(MODE)/tool/net/lfuncs.o \
o/$(MODE)/tool/net/lfinger.o \
o/$(MODE)/tool/net/lre.o \
o/$(MODE)/tool/net/ljson.o \
o/$(MODE)/tool/net/lmaxmind.o \
@ -229,6 +233,7 @@ o/$(MODE)/tool/net/redbean-demo.com.dbg: \
o/$(MODE)/tool/net/demo/unix-info.lua.zip.o \
o/$(MODE)/tool/net/demo/unix-finger.lua.zip.o \
o/$(MODE)/tool/net/demo/fetch.lua.zip.o \
o/$(MODE)/tool/net/demo/finger.lua.zip.o \
o/$(MODE)/tool/net/demo/store-asset.lua.zip.o \
o/$(MODE)/tool/net/demo/call-lua-module.lua.zip.o \
o/$(MODE)/tool/net/demo/redbean.lua.zip.o \
@ -331,6 +336,7 @@ o/$(MODE)/tool/net/redbean-unsecure.com.dbg: \
$(TOOL_NET_DEPS) \
o/$(MODE)/tool/net/redbean-unsecure.o \
o/$(MODE)/tool/net/lfuncs.o \
o/$(MODE)/tool/net/lfinger.o \
o/$(MODE)/tool/net/lre.o \
o/$(MODE)/tool/net/ljson.o \
o/$(MODE)/tool/net/lmaxmind.o \

View file

@ -112,6 +112,7 @@
#include "third_party/zlib/zlib.h"
#include "tool/args/args.h"
#include "tool/build/lib/case.h"
#include "tool/net/lfinger.h"
#include "tool/net/lfuncs.h"
#include "tool/net/ljson.h"
#include "tool/net/luacheck.h"
@ -5253,6 +5254,7 @@ static const luaL_Reg kLuaLibs[] = {
{"argon2", luaopen_argon2}, //
{"lsqlite3", luaopen_lsqlite3}, //
{"maxmind", LuaMaxmind}, //
{"finger", LuaFinger}, //
{"re", LuaRe}, //
{"unix", LuaUnix}, //
};