mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-06 11:18:30 +00:00
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:
parent
866b21a151
commit
4d25f8c3c9
75 changed files with 1551 additions and 115 deletions
|
@ -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
65
tool/net/demo/finger.lua
Normal 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
|
|
@ -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
98
tool/net/lfinger.c
Normal 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
11
tool/net/lfinger.h
Normal 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_ */
|
|
@ -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 \
|
||||
|
|
|
@ -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}, //
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue