Merge pull request #2383 from freifunk-gluon/web-fixes

gluon-web: prohibit cross-origin POST requests
This commit is contained in:
Matthias Schiffer 2022-02-03 16:49:22 +01:00 committed by GitHub
commit 68e8d32570
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 24 deletions

View File

@ -10,7 +10,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Dependencies - name: Install Dependencies
run: sudo apt install lua-check run: sudo apt-get -y update && sudo apt-get -y install lua-check
- name: Install example site - name: Install example site
run: ln -s ./docs/site-example ./site run: ln -s ./docs/site-example ./site
- name: Lint Lua code - name: Lint Lua code
@ -22,7 +22,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Dependencies - name: Install Dependencies
run: sudo apt install shellcheck run: sudo apt-get -y update && sudo apt-get -y install shellcheck
- name: Install example site - name: Install example site
run: ln -s ./docs/site-example ./site run: ln -s ./docs/site-example ./site
- name: Lint shell code - name: Lint shell code

View File

@ -44,7 +44,6 @@ $Id$
<div class="gluon-page-actions"> <div class="gluon-page-actions">
<input type="hidden" name="step" value="2" /> <input type="hidden" name="step" value="2" />
<input type="hidden" name="token" value="<%=token%>" />
<input class="gluon-button gluon-button-submit" type="submit" value="<%:Upload image%>" /> <input class="gluon-button gluon-button-submit" type="submit" value="<%:Upload image%>" />
</div> </div>
</form> </form>

View File

@ -49,13 +49,11 @@ You may obtain a copy of the License at
<form method="post" enctype="multipart/form-data" action="<%|url(request)%>" style="display:inline"> <form method="post" enctype="multipart/form-data" action="<%|url(request)%>" style="display:inline">
<input type="hidden" name="step" value="3" /> <input type="hidden" name="step" value="3" />
<input type="hidden" name="keepcfg" value="<%=keepconfig and "1" or "0"%>" /> <input type="hidden" name="keepcfg" value="<%=keepconfig and "1" or "0"%>" />
<input type="hidden" name="token" value="<%=token%>" />
<input class="gluon-button gluon-button-submit" type="submit" value="<%:Continue%>" /> <input class="gluon-button gluon-button-submit" type="submit" value="<%:Continue%>" />
</form> </form>
<form method="post" enctype="multipart/form-data" action="<%|url(request)%>" style="display:inline"> <form method="post" enctype="multipart/form-data" action="<%|url(request)%>" style="display:inline">
<input type="hidden" name="step" value="1" /> <input type="hidden" name="step" value="1" />
<input type="hidden" name="keepcfg" value="<%=keepconfig and "1" or "0"%>" /> <input type="hidden" name="keepcfg" value="<%=keepconfig and "1" or "0"%>" />
<input type="hidden" name="token" value="<%=token%>" />
<input class="gluon-button gluon-button-reset" type="submit" value="<%:Cancel%>" /> <input class="gluon-button gluon-button-reset" type="submit" value="<%:Cancel%>" />
</form> </form>
</div> </div>

View File

@ -1,5 +1,4 @@
<form method="post" enctype="multipart/form-data" action="<%|url(request)%>" data-update="reset"> <form method="post" enctype="multipart/form-data" action="<%|url(request)%>" data-update="reset">
<input type="hidden" name="token" value="<%=token%>" />
<input type="hidden" name="<%=id%>" value="1" /> <input type="hidden" name="<%=id%>" value="1" />
<div class="gluon-form" id="form-<%=id%>"> <div class="gluon-form" id="form-<%=id%>">

View File

@ -184,9 +184,15 @@ local function dispatch(config, http, request)
return return
end end
http:parse_input(node.filehandler) local ok, err = pcall(http.parse_input, http, node.filehandler)
if not ok then
http:status(400, "Bad request")
http:prepare_content("text/plain")
http:write(err .. "\r\n")
return
end
local ok, err = pcall(node.target) ok, err = pcall(node.target)
if not ok then if not ok then
http:status(500, "Internal Server Error") http:status(500, "Internal Server Error")
renderer.render_layout("error/500", { renderer.render_layout("error/500", {
@ -208,6 +214,6 @@ return function(config, http)
if not ok then if not ok then
http:status(500, "Internal Server Error") http:status(500, "Internal Server Error")
http:prepare_content("text/plain") http:prepare_content("text/plain")
http:write(err) http:write(err .. "\r\n")
end end
end end

View File

@ -108,16 +108,11 @@ end
-- o String value containing a chunk of the file data -- o String value containing a chunk of the file data
-- o Boolean which indicates whether the current chunk is the last one (eof) -- o Boolean which indicates whether the current chunk is the last one (eof)
local function mimedecode_message_body(src, msg, filecb) local function mimedecode_message_body(src, msg, filecb)
local mime_boundary = (msg.env.CONTENT_TYPE or ''):match("^multipart/form%-data; boundary=(.+)$")
if msg and msg.env.CONTENT_TYPE then if not mime_boundary then
msg.mime_boundary = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)$") error("Invalid Content-Type found")
end end
if not msg.mime_boundary then
return nil, "Invalid Content-Type found"
end
local tlen = 0 local tlen = 0
local inhdr = false local inhdr = false
local field = nil local field = nil
@ -188,10 +183,10 @@ local function mimedecode_message_body(src, msg, filecb)
local spos, epos, found local spos, epos, found
repeat repeat
spos, epos = data:find("\r\n--" .. msg.mime_boundary .. "\r\n", 1, true) spos, epos = data:find("\r\n--" .. mime_boundary .. "\r\n", 1, true)
if not spos then if not spos then
spos, epos = data:find("\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true) spos, epos = data:find("\r\n--" .. mime_boundary .. "--\r\n", 1, true)
end end
@ -250,20 +245,61 @@ local function mimedecode_message_body(src, msg, filecb)
return true return true
end end
return pump(src, snk) assert(pump(src, snk))
end
local function check_post_origin(msg)
local default_port = '80'
local request_scheme = 'http'
if msg.env.HTTPS then
default_port = '443'
request_scheme = 'https'
end
local request_host = msg.env.HTTP_HOST
if not request_host then
error('POST request without Host header')
end
if not request_host:match(':[0-9]+$') then
request_host = request_host .. ':' .. default_port
end
local origin = msg.env.HTTP_ORIGIN
if not origin then
error('POST request without Origin header')
end
local origin_scheme, origin_host = origin:match('^([^:]*)://(.*)$')
if not origin_host then
error('POST request with invalid Origin header')
end
if not origin_host:match(':[0-9]+$') then
local origin_port
if origin_scheme == 'http' then
origin_port = '80'
elseif origin_scheme == 'https' then
origin_port = '443'
else
error('POST request with invalid Origin header')
end
origin_host = origin_host .. ':' .. origin_port
end
if request_scheme ~= origin_scheme or request_host ~= origin_host then
error('Invalid cross-origin POST')
end
end end
-- This function will examine the Content-Type within the given message object -- This function will examine the Content-Type within the given message object
-- to select the appropriate content decoder. -- to select the appropriate content decoder.
-- Currently only the multipart/form-data mime type is supported. -- Currently only the multipart/form-data mime type is supported.
function M.parse_message_body(src, msg, filecb) function M.parse_message_body(src, msg, filecb)
if not (msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE) then if msg.env.REQUEST_METHOD ~= "POST" then
return return
end end
if msg.env.CONTENT_TYPE:match("^multipart/form%-data") then check_post_origin(msg)
return mimedecode_message_body(src, msg, filecb)
end mimedecode_message_body(src, msg, filecb)
end end
return M return M