fix: possible DoS, as reported by John Page (aka hyp3rlinx) ApparitionSec (CVE-2020-13432)

This commit is contained in:
Massimo Melina 2020-05-22 14:03:39 +02:00
parent 16744d7c6c
commit a3056ccfd4
4 changed files with 85 additions and 21 deletions

View File

@ -39,7 +39,7 @@ COMMENT with the ones above you can disable some features of the template. They
</head> </head>
<body> <body>
<div id="wrapper"> <div id="wrapper">
<!--{.comment|--><h1 style='margin-bottom:100em'>WARNING: this template is only to be used with HFS 2.3 (and macros enabled)</h1> <!--.} --> <!--{.comment|--><h1 style='margin-bottom:100em'>WARNING: this template is only to be used with HFS 2.4 (and macros enabled)</h1> <!--.} -->
{.$menu panel.} {.$menu panel.}
{.$folder panel.} {.$folder panel.}
{.$list panel.} {.$list panel.}

View File

@ -19,6 +19,7 @@ Copyright (C) 2002-2014 Massimo Melina (www.rejetto.com)
HTTP Server Lib HTTP Server Lib
==== TO DO ==== TO DO
* https
* upload bandwidth control (can it be done without multi-threading?) * upload bandwidth control (can it be done without multi-threading?)
} }
@ -292,7 +293,7 @@ const
MINIMUM_CHUNK_SIZE = 2*1024; MINIMUM_CHUNK_SIZE = 2*1024;
MAXIMUM_CHUNK_SIZE = 1024*1024; MAXIMUM_CHUNK_SIZE = 1024*1024;
HRM2CODE: array [ThttpReplyMode] of integer = (200, 200, 403, 401, 404, 400, HRM2CODE: array [ThttpReplyMode] of integer = (200, 200, 403, 401, 404, 400,
500, 0, 0, 405, 302, 503, 413, 301, 304 ); 500, 0, 0, 405, 302, 429, 413, 301, 304 );
METHOD2STR: array [ThttpMethod] of ansistring = ('UNK','GET','POST','HEAD'); METHOD2STR: array [ThttpMethod] of ansistring = ('UNK','GET','POST','HEAD');
HRM2STR: array [ThttpReplyMode] of ansistring = ('Head+Body', 'Head only', 'Deny', HRM2STR: array [ThttpReplyMode] of ansistring = ('Head+Body', 'Head only', 'Deny',
'Unauthorized', 'Not found', 'Bad request', 'Internal error', 'Close', 'Unauthorized', 'Not found', 'Bad request', 'Internal error', 'Close',
@ -352,7 +353,7 @@ const
'', '',
'405 - Method not allowed', '405 - Method not allowed',
'<html><head><meta http-equiv="refresh" content="url=%url%" /></head><body onload=''window.location="%url%"''>302 - <a href="%url%">Redirection to %url%</a></body></html>', '<html><head><meta http-equiv="refresh" content="url=%url%" /></head><body onload=''window.location="%url%"''>302 - <a href="%url%">Redirection to %url%</a></body></html>',
'503 - Server is overloaded, retry later', '429 - Server is overloaded, retry later',
'413 - The request has exceeded the max length allowed', '413 - The request has exceeded the max length allowed',
'301 - Moved permanently to <a href="%url%">%url%</a>', '301 - Moved permanently to <a href="%url%">%url%</a>',
'' // RFC2616: The 304 response MUST NOT contain a message-body '' // RFC2616: The 304 response MUST NOT contain a message-body
@ -875,7 +876,6 @@ end; // timerEvent
procedure ThttpSrv.notify(ev:ThttpEvent; conn:ThttpConn); procedure ThttpSrv.notify(ev:ThttpEvent; conn:ThttpConn);
begin begin
if not assigned(onEvent) then exit; if not assigned(onEvent) then exit;
//if assigned(sock) then sock.pause();
if assigned(conn) then if assigned(conn) then
begin begin
inc(conn.lockCount); inc(conn.lockCount);

View File

@ -36,7 +36,7 @@ uses
HSlib, traylib, monoLib, progFrmLib, classesLib; HSlib, traylib, monoLib, progFrmLib, classesLib;
const const
VERSION = '2.4.0 beta10'; VERSION = '2.4.0 RC1';
VERSION_BUILD = '312'; VERSION_BUILD = '312';
VERSION_STABLE = {$IFDEF STABLE } TRUE {$ELSE} FALSE {$ENDIF}; VERSION_STABLE = {$IFDEF STABLE } TRUE {$ELSE} FALSE {$ENDIF};
CURRENT_VFS_FORMAT :integer = 1; CURRENT_VFS_FORMAT :integer = 1;
@ -3458,7 +3458,6 @@ end; // shouldRecur
function Tmainfrm.getFolderPage(folder:Tfile; cd:TconnData; otpl:Tobject):string; function Tmainfrm.getFolderPage(folder:Tfile; cd:TconnData; otpl:Tobject):string;
// we pass the Tpl parameter as Tobject because symbol Ttpl is not defined yet // we pass the Tpl parameter as Tobject because symbol Ttpl is not defined yet
var var
baseurl, list, fileTpl, folderTpl, linkTpl: string; baseurl, list, fileTpl, folderTpl, linkTpl: string;
table: TStringDynArray; table: TStringDynArray;
@ -3606,6 +3605,39 @@ var
fast.append(s); fast.append(s);
end; // handleItem end; // handleItem
const ip2availability: Tdictionary<string,Tdatetime> = NIL;
const folderConcurrents: integer = 0;
procedure updateAvailability();
var
pair: Tpair<string,Tdatetime>;
t: Tdatetime;
begin
dec(folderConcurrents);
t:=now();
ip2availability[cd.address]:=t+1/SECONDS;
// purge leftovers
for pair in ip2availability do
if pair.Value < t then
ip2availability.Remove(pair.Key);
end;
function available():boolean;
begin
if ip2availability = NIL then
ip2availability:=Tdictionary<string,Tdatetime>.create();
try
if ip2availability[cd.address] > now() then // this specific address has to wait?
exit(FALSE);
except
end;
if folderConcurrents >= 3 then // max number of concurrent folder loading, others are postponed
exit(FALSE);
inc(folderConcurrents);
ip2availability.AddOrSetValue(cd.address, now()+1);
result:=TRUE;
end; // available
var var
i, n: integer; i, n: integer;
f: Tfile; f: Tfile;
@ -3613,6 +3645,12 @@ begin
result:=''; result:='';
if (folder = NIL) or not folder.isFolder() then exit; if (folder = NIL) or not folder.isFolder() then exit;
if not available() then
begin
cd.conn.reply.mode:=HRM_OVERLOAD;
cd.conn.addHeader('Refresh: '+intToStr(1+random(2))); // random for less collisions
exit('Please wait, server busy');
end;
if macrosLogChk.checked and not appendmacroslog1.checked then if macrosLogChk.checked and not appendmacroslog1.checked then
resetLog(); resetLog();
diffTpl:=Ttpl.create(); diffTpl:=Ttpl.create();
@ -3735,6 +3773,7 @@ try
result:=replaceText(result, '%build-time%', result:=replaceText(result, '%build-time%',
floatToStrF((now()-buildTime)*SECONDS, ffFixed, 7,3) ); floatToStrF((now()-buildTime)*SECONDS, ffFixed, 7,3) );
finally finally
updateAvailability();
folder.unlock(); folder.unlock();
diffTpl.free; diffTpl.free;
end; end;
@ -5184,6 +5223,7 @@ var
if conn.reply.contentType = '' then if conn.reply.contentType = '' then
conn.reply.contentType:=ansistring(if_(trim(getTill('<', s))='', 'text/html', 'text/plain'))+'; charset=utf-8'; conn.reply.contentType:=ansistring(if_(trim(getTill('<', s))='', 'text/html', 'text/plain'))+'; charset=utf-8';
if conn.reply.mode = HRM_IGNORE then
conn.reply.mode:=HRM_REPLY; conn.reply.mode:=HRM_REPLY;
conn.reply.bodyMode:=RBM_STRING; conn.reply.bodyMode:=RBM_STRING;
conn.reply.body:=UTF8encode(s); conn.reply.body:=UTF8encode(s);
@ -5427,6 +5467,12 @@ var
if conn.reply.mode = HRM_REDIRECT then if conn.reply.mode = HRM_REDIRECT then
exit; exit;
lastActivityTime:=now();
if conn.request.method = HM_HEAD then
conn.reply.mode:=HRM_REPLY_HEADER
else
conn.reply.mode:=HRM_REPLY;
if ansiStartsStr('/~img', url) then if ansiStartsStr('/~img', url) then
begin begin
if not sendPic(data) then if not sendPic(data) then
@ -5579,6 +5625,8 @@ var
if ansiStartsStr('~files.lst', urlCmd) if ansiStartsStr('~files.lst', urlCmd)
or f.isFolder() and (data.urlvars.values['tpl'] = 'list') then or f.isFolder() and (data.urlvars.values['tpl'] = 'list') then
begin begin
if conn.reply.mode=HRM_REPLY_HEADER then
exit;
// load from external file // load from external file
s:=cfgPath+FILELIST_TPL_FILE; s:=cfgPath+FILELIST_TPL_FILE;
if newMtime(s, lastFilelistTpl) then if newMtime(s, lastFilelistTpl) then
@ -5605,19 +5653,12 @@ var
exit; exit;
end; end;
case conn.request.method of
HM_GET, HM_POST:
begin
conn.reply.mode:=HRM_REPLY;
lastActivityTime:=now();
end;
HM_HEAD: conn.reply.mode:=HRM_REPLY_HEADER;
end;
data.lastFile:=f; // auto-freeing data.lastFile:=f; // auto-freeing
if f.isFolder() then if f.isFolder() then
begin begin
if conn.reply.mode=HRM_REPLY_HEADER then
exit;
deletion(); deletion();
if sessionRedirect() then if sessionRedirect() then
exit; exit;

View File

@ -166,7 +166,7 @@ function replaceString(var ss:TStringDynArray; old, new:string):integer;
function popString(var ss:TstringDynArray):string; function popString(var ss:TstringDynArray):string;
procedure insertString(s:string; idx:integer; var ss:TStringDynArray); procedure insertString(s:string; idx:integer; var ss:TStringDynArray);
function removeString(var a:TStringDynArray; idx:integer; l:integer=1):boolean; overload; function removeString(var a:TStringDynArray; idx:integer; l:integer=1):boolean; overload;
function removeString(find:string; var a:TStringDynArray):boolean; overload; function removeString(s:string; var a:TStringDynArray; onlyOnce:boolean=TRUE; ci:boolean=TRUE; keepOrder:boolean=TRUE):boolean; overload;
procedure removeStrings(find:string; var a:TStringDynArray); procedure removeStrings(find:string; var a:TStringDynArray);
procedure toggleString(s:string; var ss:TStringDynArray); procedure toggleString(s:string; var ss:TStringDynArray);
function onlyString(s:string; ss:TStringDynArray):boolean; function onlyString(s:string; ss:TStringDynArray):boolean;
@ -669,10 +669,6 @@ begin
until false; until false;
end; // removeStrings end; // removeStrings
// remove first instance of the specified string
function removeString(find:string; var a:TStringDynArray):boolean;
begin result:=removeString(a, idxOf(find,a)) end;
function removeArray(var src:TstringDynArray; toRemove:array of string):integer; function removeArray(var src:TstringDynArray; toRemove:array of string):integer;
var var
i, l, ofs: integer; i, l, ofs: integer;
@ -746,6 +742,33 @@ while idx+l < length(a) do
setLength(a, idx); setLength(a, idx);
end; // removestring end; // removestring
function removeString(s:string; var a:TStringDynArray; onlyOnce:boolean=TRUE; ci:boolean=TRUE; keepOrder:boolean=TRUE):boolean; overload;
var i, lessen:integer;
begin
result:=FALSE;
lessen:=0;
try
for i:=length(a)-1 to 0 do
if ci and sameText(a[i], s)
or not ci and (a[i]=s) then
begin
result:=TRUE;
if keepOrder then
removeString(a, i)
else
begin
inc(lessen);
a[i]:=a[length(a)-lessen];
end;
if onlyOnce then
exit;
end;
finally
if lessen > 0 then
setLength(a, length(a)-lessen);
end;
end;
function dotted(i:int64):string; function dotted(i:int64):string;
begin begin
result:=intToStr(i); result:=intToStr(i);