-
Notifications
You must be signed in to change notification settings - Fork 137
Can not SET variable with same name as POST param #342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The documentation is currently not super clear:
The behavior is:
This has always been the case, and changing it would break applications. But I agree it's confusing, improperly documented, and could lead to security vulnerabilities if used improperly. The solution for you in the short term is to use But in the long term, I'm ambivalent about what should be done. What do you think? |
Ah so, would I be correct in thinking the "safest" thing to do for now is use SET :x = 'safe/value.sql';
SELECT 'dynamic' AS component, sqlpage.run_sql(:x) AS properties; -- safe because of prior SET |
A related observation: though I see I've used |
Ah OK, from the docs, my takeaway was to use $x everywhere unless I wanted to specifically discriminate between GET and POST params. But if SET is setting only the GET param (is it?) when using Here are my thoughts on current behaviour and potential improvements:
Proposal:
1. It is not clear to me is what
|
Hello and thank you for your thoughts! We cannot really use new and sqlpage-specific variable syntax, because we wouldn't be able to parse them with a standard sql parser, and because they already have their own semantics ( I have another, slightly less ambitious but more realistic proposition:
|
Ah, I didn't realize $ and : where not already sqlpage specific. I mean I knew : is used for placeholders in prepared statements. I guess you have a thinner wrapper around pure SQL than I realized. I see your point about !. Same would go for & too bitwise and or something. I guess if we don't introduce any sqlpage specific we're stuck with overloading existing operators that won't mess up the parser. I like your proposal, it would a big improvement. I think it still lacks ability to create variables that can only be set internally. Would you have any ideas for that could be done? I think it's not required if the dev just sets variables before using them. It would be nice if there was no room for error though... |
Do you see a concrete case when reusing the same namespace as get variables would be problematic? I mean, the user being able to overwrite the programmer's variables like it is today is terrible, as you have highlighted already. But when this is fixed, I don't feel like we really need more. |
I don't feel very strongly about either of these cases, but since you ask: Case 1 (security impact) - unset var-- oops, I never set :inner, now user can dictate which sql is run or worse if sqlpage.exec
SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties; Case 1 can be hit without being detected if the var is unset only in rare cases, or if the error coming back from sqlpage.run_sql is squashed (not sure if that is possible). Being unset (and so NULL) would potentially be invisible but could be exploited. Case 2 (convenience) - value swapConsider some SQL (or function if db supports it) -- pollute the namespace with :x and :changed
SET ":x" = normalize(:var);
SET ":changed" = :x <> :var;
-- assign the normalized value back
SET ":var" = :x;
-- redirect only if URL changed after normalization
SET ":inner" = CASE :changed
WHEN TRUE THEN 'redirect.sql'
ELSE 'continue.sql'
END;
SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties; It would be much nicer (in my opinion) -- using my notation proposed above for consistency
SET "!var" = normalize(:var);
SET ":inner" = CASE "!var" <> :var
WHEN TRUE THEN 'redirect.sql'
ELSE 'continue.sql'
END;
-- no need to assign back if references to $var later are like COALESCE(!var, :var, ?var)
SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties; NOTE: while typing this, I came up with a solution to the value swap using JSON, which is elegant enough for me. I'll probably use that moving forward unless there is a more elegant way to do it that you recommend. My conclusions (rambling for my own benefit - typing it out helps me think about if it makes any sense at all)...Case 1 - unset var: not a huge risk. Case 2 - value swap: it's a pain in all languages, but somehow seems particularly clunky in SQL... If we had multiple return values I could do something like: -- pollute the namespace with :changed
SET ":var", ":changed" = normalize(:var); -- postgresql pgSQL ext has SELECT INTO var1, var2, var3 ...
SET ":inner" = CASE ":changed"
WHEN TRUE THEN 'redirect.sql'
ELSE 'continue.sql'
END;
SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties; Or I guess we can simulate multiple return values via JSON: -- pollute the namespace with :ret
SET ":ret" = normalize(:var);
SET ":var" = :ret->>'var';
SET ":inner" = CASE ":ret"->>'changed';
WHEN 'true' THEN 'redirect.sql'
ELSE 'continue.sql'
END;
SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties; Even if we could declare methods SQLPage (client) side... DECLARE normalize_and_maybe_redir(var) AS $$
-- local temp var so less namespace pollution (I guess inner scope sees :x)
SET ":x" = normalize(var);
-- assign the normalized value back (only for benefit of inner scope)
SET ":var" = :x;
-- redirect only if URL changed after normalization
SET ":inner" = CASE "!var" <> :var
WHEN TRUE THEN 'redirect.sql'
ELSE THEN 'empty.sql'
END;
SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties;
-- ideally could pass in :x instead of :var as an argument to run_sql
-- SELECT 'dynamic' AS component, sqlpage.run_sql(:inner, var=:x) AS properties;
RETURN :x;
$$ LANGUAGE sqlpage;
:var = normalize_and_maybe_redir(var);
:var2 = normalize_and_maybe_redir(var2); ... it still looks so clunky. I'm honestly not sure why. Am I missing something? The JSON solution to the value swap is probably a decent solution and reads better than most of them, and doesn't require any changes. |
@djyotta , can you test v0.22 ? |
Yes, I will. I've been sick. Still recovering. Will do a test as soon as possible. |
I wish you a prompt recovery! |
Thanks @lovasoa . I'm feeling a bunch better already. But it must have been something nasty because it took 4 days before I felt like I could function normally again... I did some tests. I used the following SQL to create a test form to GET and POST. SET ":post" = sqlpage.variables('post');
SET ":get" = sqlpage.variables('get');
-- set GET variable
SET $x = 'get';
-- set POST variable
SET ":y" = 'post';
-- set POST variable
SELECT 'text' AS component, CASE COALESCE($x, '')
WHEN 'get-blah' THEN 'Fail - $x is overriden by GET param'
WHEN 'post-blah' THEN 'Fail - $x is overriden by POST param'
WHEN 'get' THEN 'Success - assigning $x worked'
WHEN '' THEN 'Nothing assigned to $x'
ELSE 'Fail - assigning $x failed for an unknown reason'
END AS contents;
SELECT 'text' AS component, CASE COALESCE(:y, '')
WHEN 'get-blah' THEN 'Fail - :y is overriden by GET param'
WHEN 'post-blah' THEN 'Fail - :y is overriden by POST param'
WHEN 'post' THEN 'Success - assigning :y worked'
WHEN '' THEN 'Nothing assigned to :y'
ELSE 'Fail - assigning :y failed for an unknown reason'
END AS contents;
SELECT 'debug' AS component;
SELECT $get AS "GET Params";
SELECT :post AS "POST Params";
SELECT 'table' AS component;
SELECT * FROM json_each(sqlpage.variables()) ORDER BY fullkey;
DROP TABLE IF EXISTS test_cases;
CREATE TEMPORARY TABLE test_cases(
id,
type text,
var text
);
INSERT INTO test_cases VALUES (1, 'get', 'x'), (2, 'get', 'y');
INSERT INTO test_cases VALUES (1, 'post', 'x'), (2, 'post', 'y');
SELECT 'button' AS component;
SELECT '/test.sql' AS link, 'Clear' AS title;
SELECT 'form' AS component, 'get' AS method;
SELECT 'submit' AS type, var AS name, 'Test GET Case '||id AS label, 'GET '||var||'=get-blah' AS value FROM test_cases WHERE type = 'get';
SELECT 'form' AS component, 'post' AS method;
SELECT 'submit' AS type, var AS name, 'Test POST Case '||id AS label, 'POST '||var||'=post-blah' AS value FROM test_cases WHERE type = 'post'; I observed the bug is present on v0.20.4 and fixed in 0.22.0
I do wonder about the warning messages though. In Test POST Case 1, I get Otherwise testing seems ok. I've already put some of my production apps on v0.22.0 and they are working fine (but just lots of warnings as I haven't yet fixed my apps to reference You would know better than me if it's behaving as you intended |
Introduction
Given the following a POST is made with param
content=blah
to a sqlpage:Then the output is like this:
However, if the post param does not have the same name as the variable assigned:
Then the output is like this:
This is particularly dangerous when using variables internally to run_sql. If any of those variable names are POSTed then the attacker has full control over which sql is run.
Even this is not safe as the sql is run regardless of the where clause:
Aside from the security issue, it causes a headache when variable assignment is seemingly of none effect.
Note, this doesn't seem to affect GET , only POST
Version information
Additional context
Add any other context about the problem here.
The text was updated successfully, but these errors were encountered: