basic input sanitization w/ todos sprinkled everywhere
This commit is contained in:
parent
0fddc7a3e6
commit
a7faa055f9
|
@ -9,6 +9,7 @@ require "./lib/hash"
|
||||||
require "./lib/format"
|
require "./lib/format"
|
||||||
require "./lib/accounts"
|
require "./lib/accounts"
|
||||||
require "./lib/gjp"
|
require "./lib/gjp"
|
||||||
|
require "./lib/clean"
|
||||||
|
|
||||||
Dotenv.load
|
Dotenv.load
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ CrystalGauntlet.endpoints["/accounts/loginGJAccount.php"] = ->(body : String): S
|
||||||
bcrypt = Crypto::Bcrypt::Password.new(hash)
|
bcrypt = Crypto::Bcrypt::Password.new(hash)
|
||||||
|
|
||||||
if bcrypt.verify(password)
|
if bcrypt.verify(password)
|
||||||
user_id = Accounts.get_user_id(username, account_id.to_s)
|
user_id = Accounts.get_user_id(account_id.to_s)
|
||||||
"#{account_id},#{user_id}"
|
"#{account_id},#{user_id}"
|
||||||
else
|
else
|
||||||
return "-12"
|
return "-12"
|
||||||
|
|
|
@ -8,7 +8,7 @@ CrystalGauntlet.endpoints["/accounts/registerGJAccount.php"] = ->(body : String)
|
||||||
params = URI::Params.parse(body)
|
params = URI::Params.parse(body)
|
||||||
puts params.inspect
|
puts params.inspect
|
||||||
|
|
||||||
username = params["userName"]
|
username = Clean.clean_special(params["userName"])
|
||||||
password = params["password"]
|
password = params["password"]
|
||||||
email = params["email"]
|
email = params["email"]
|
||||||
|
|
||||||
|
|
|
@ -10,23 +10,44 @@ CrystalGauntlet.endpoints["/uploadGJLevel21.php"] = ->(body : String): String {
|
||||||
if ext_id == "-1" || !Accounts.verify_gjp(ext_id, params["gjp"])
|
if ext_id == "-1" || !Accounts.verify_gjp(ext_id, params["gjp"])
|
||||||
return "-1"
|
return "-1"
|
||||||
end
|
end
|
||||||
user_id = Accounts.get_user_id(params["userName"], ext_id)
|
user_id = Accounts.get_user_id(ext_id)
|
||||||
|
|
||||||
song_id = params["songID"] == "0" ? params["audioTrack"] : params["songID"]
|
song_id = params["songID"] == "0" ? params["audioTrack"] : params["songID"]
|
||||||
|
|
||||||
description = params["levelDesc"]
|
description = params["levelDesc"]
|
||||||
if params["gameVersion"].to_i >= 20 # 2.0
|
if params["gameVersion"].to_i >= 20 # 2.0
|
||||||
description = GDBase64.decode_string description
|
description = Clean.clean_special_lenient(GDBase64.decode_string description)
|
||||||
|
else
|
||||||
|
description = Clean.clean_special_lenient(description)
|
||||||
end
|
end
|
||||||
|
# todo: patch descriptions to prevent color bugs
|
||||||
|
|
||||||
|
# todo: use 1.9 levelInfo..?
|
||||||
|
# https://github.com/Cvolton/GMDprivateServer/blob/master/incl/levels/uploadGJLevel.php#L56
|
||||||
|
|
||||||
|
# todo: use 2.2 unlisted
|
||||||
|
|
||||||
|
# todo: https://github.com/Cvolton/GMDprivateServer/blob/master/incl/levels/uploadGJLevel.php#L53
|
||||||
|
extraString = params["extraString"]? || "29_29_29_40_29_29_29_29_29_29_29_29_29_29_29_29"
|
||||||
|
|
||||||
|
# todo: cap level length
|
||||||
|
# todo: cap coins
|
||||||
|
# todo: cap ldm to bool
|
||||||
|
# todo: cap twoplayer to bool
|
||||||
|
# todo: cap unlisted to bool
|
||||||
|
# todo: cap requested stars
|
||||||
|
|
||||||
|
# todo: verify object count, coins and twoplayer (i'm sure it's possible)
|
||||||
|
|
||||||
if DATABASE.scalar("select count(*) from levels where id = ? and user_id = ?", params["levelID"], params["accountID"]).as(Int64) > 0
|
if DATABASE.scalar("select count(*) from levels where id = ? and user_id = ?", params["levelID"], params["accountID"]).as(Int64) > 0
|
||||||
# update existing level
|
# update existing level
|
||||||
|
# todo
|
||||||
raise "not implemented"
|
raise "not implemented"
|
||||||
else
|
else
|
||||||
# create new level
|
# create new level
|
||||||
next_id = (DATABASE.scalar("select max(id) from levels").as(Int64 | Nil) || 0) + 1
|
next_id = (DATABASE.scalar("select max(id) from levels").as(Int64 | Nil) || 0) + 1
|
||||||
|
|
||||||
DATABASE.exec("insert into levels (id, name, user_id, description, original, game_version, binary_version, password, requested_stars, unlisted, version, level_data, extra_data, level_info, wt1, wt2, song_id, length, objects, coins, has_ldm, two_player) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", next_id, params["levelName"], user_id, description, params["original"].to_i32, params["gameVersion"].to_i32, params["binaryVersion"].to_i32, params["password"] == "0" ? nil : params["password"].to_i32, params["requestedStars"].to_i32, params["unlisted"].to_i32, params["levelVersion"].to_i32, params["levelString"], params["extraString"], params["levelInfo"], params["wt"], params["wt2"], song_id.to_i32, params["levelLength"].to_i32, params["objects"].to_i32, params["coins"].to_i32, params["ldm"].to_i32, params["twoPlayer"].to_i32)
|
DATABASE.exec("insert into levels (id, name, user_id, description, original, game_version, binary_version, password, requested_stars, unlisted, version, level_data, extra_data, level_info, wt1, wt2, song_id, length, objects, coins, has_ldm, two_player) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", next_id, Clean.clean_special(params["levelName"]), user_id, description, params["original"].to_i32, params["gameVersion"].to_i32, params["binaryVersion"].to_i32, params["password"] == "0" ? nil : params["password"].to_i32, params["requestedStars"].to_i32, params["unlisted"].to_i32, params["levelVersion"].to_i32, Clean.clean_b64(params["levelString"]), Clean.clean_special(extraString), Clean.clean_b64(params["levelInfo"]), Clean.clean_number(params["wt"]), Clean.clean_number(params["wt2"]), song_id.to_i32, params["levelLength"].to_i32, params["objects"].to_i32, params["coins"].to_i32, params["ldm"].to_i32, params["twoPlayer"].to_i32)
|
||||||
|
|
||||||
next_id.to_s
|
next_id.to_s
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,6 +41,7 @@ CrystalGauntlet.endpoints["/getGJUserInfo20.php"] = ->(body : String): String {
|
||||||
spider = rs.read(Int32)
|
spider = rs.read(Int32)
|
||||||
explosion = rs.read(Int32)
|
explosion = rs.read(Int32)
|
||||||
|
|
||||||
|
# "1:".$user["userName"].":2:".$user["userID"].":13:".$user["coins"].":17:".$user["userCoins"].":10:".$user["color1"].":11:".$user["color2"].":3:".$user["stars"].":46:".$user["diamonds"].":4:".$user["demons"].":8:".$creatorpoints.":18:".$msgstate.":19:".$reqsstate.":50:".$commentstate.":20:".$accinfo["youtubeurl"].":21:".$user["accIcon"].":22:".$user["accShip"].":23:".$user["accBall"].":24:".$user["accBird"].":25:".$user["accDart"].":26:".$user["accRobot"].":28:".$user["accGlow"].":43:".$user["accSpider"].":47:".$user["accExplosion"].":30:".$rank.":16:".$user["extID"].":31:".$friendstate.":44:".$accinfo["twitter"].":45:".$accinfo["twitch"].":29:1:49:".$badge . $appendix;
|
||||||
return CrystalGauntlet::Format.fmt_hash({
|
return CrystalGauntlet::Format.fmt_hash({
|
||||||
1 => username,
|
1 => username,
|
||||||
2 => user_id,
|
2 => user_id,
|
||||||
|
@ -80,10 +81,8 @@ CrystalGauntlet.endpoints["/getGJUserInfo20.php"] = ->(body : String): String {
|
||||||
# badge, todo
|
# badge, todo
|
||||||
49 => 0
|
49 => 0
|
||||||
})
|
})
|
||||||
|
else
|
||||||
|
"-1"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
"-1"
|
|
||||||
|
|
||||||
# echo "1:".$user["userName"].":2:".$user["userID"].":13:".$user["coins"].":17:".$user["userCoins"].":10:".$user["color1"].":11:".$user["color2"].":3:".$user["stars"].":46:".$user["diamonds"].":4:".$user["demons"].":8:".$creatorpoints.":18:".$msgstate.":19:".$reqsstate.":50:".$commentstate.":20:".$accinfo["youtubeurl"].":21:".$user["accIcon"].":22:".$user["accShip"].":23:".$user["accBall"].":24:".$user["accBird"].":25:".$user["accDart"].":26:".$user["accRobot"].":28:".$user["accGlow"].":43:".$user["accSpider"].":47:".$user["accExplosion"].":30:".$rank.":16:".$user["extID"].":31:".$friendstate.":44:".$accinfo["twitter"].":45:".$accinfo["twitch"].":29:1:49:".$badge . $appendix;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,13 @@ CrystalGauntlet.endpoints["/updateGJUserScore22.php"] = ->(body : String): Strin
|
||||||
return "-1"
|
return "-1"
|
||||||
end
|
end
|
||||||
|
|
||||||
user_id = Accounts.get_user_id(params["userName"], account_id)
|
user_id = Accounts.get_user_id(account_id)
|
||||||
|
|
||||||
DATABASE.exec("update users set username=?, stars=?, demons=?, coins=?, user_coins=?, diamonds=?, icon_type=?, color1=?, color2=?, cube=?, ship=?, ball=?, ufo=?, wave=?, robot=?, spider=?, explosion=?, special=?, glow=?, last_played=? where id=?", params["userName"], params["stars"], params["demons"], params["coins"], params["userCoins"], params["diamonds"], params["iconType"], params["color1"], params["color2"], params["accIcon"], params["accShip"], params["accBall"], params["accBird"], params["accDart"], params["accRobot"], params["accSpider"], params["accExplosion"], params["special"], params["accGlow"], Time.utc.to_s("%Y-%m-%d %H:%M:%S"), user_id)
|
# todo: prevent username change unless it's a capitalization change
|
||||||
|
# todo: keep track of stat changes to look out for leaderboard cheating & whatnot
|
||||||
|
# todo: cap out demon count at the current amount of uploaded demons? same for stars & user coins. could be expensive though
|
||||||
|
|
||||||
|
DATABASE.exec("update users set username=?, stars=?, demons=?, coins=?, user_coins=?, diamonds=?, icon_type=?, color1=?, color2=?, cube=?, ship=?, ball=?, ufo=?, wave=?, robot=?, spider=?, explosion=?, special=?, glow=?, last_played=? where id=?", params["userName"], params["stars"].to_i32, params["demons"].to_i32, params["coins"].to_i32, params["userCoins"].to_i32, params["diamonds"].to_i32, params["iconType"].to_i32, params["color1"].to_i32, params["color2"].to_i32, params["accIcon"].to_i32, params["accShip"].to_i32, params["accBall"].to_i32, params["accBird"].to_i32, params["accDart"].to_i32, params["accRobot"].to_i32, params["accSpider"].to_i32, params["accExplosion"].to_i32, params["special"].to_i32, params["accGlow"].to_i32, Time.utc.to_s("%Y-%m-%d %H:%M:%S"), user_id)
|
||||||
|
|
||||||
user_id.to_s
|
user_id.to_s
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ module CrystalGauntlet::Accounts
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_user_id(username : String, ext_id : String) : Int32
|
def get_user_id(ext_id : String) : Int32
|
||||||
DATABASE.query("select id from users where udid = ? or account_id = ?", ext_id, ext_id) do |rs|
|
DATABASE.query("select id from users where udid = ? or account_id = ?", ext_id, ext_id) do |rs|
|
||||||
if rs.move_next
|
if rs.move_next
|
||||||
return rs.read(Int32)
|
return rs.read(Int32)
|
||||||
|
|
32
src/lib/clean.cr
Normal file
32
src/lib/clean.cr
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# utilities to prevent malicious user input
|
||||||
|
module CrystalGauntlet::Clean
|
||||||
|
extend self
|
||||||
|
|
||||||
|
# removes commonly used chars in response formatting
|
||||||
|
def clean_special(str)
|
||||||
|
# these are just the ones commonly used in response formatting
|
||||||
|
# i'm unsure if any other ones should be added, so for the time
|
||||||
|
# being i'll just keep it as is
|
||||||
|
str.gsub(/[:\|~#\(\)\0\n]/, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
# for descriptions & similar
|
||||||
|
def clean_special_lenient(str)
|
||||||
|
str.gsub(/[\0]/, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
# only allow alphanumeric chars & space
|
||||||
|
def clean_char(str)
|
||||||
|
str.gsub(/[^A-Za-z0-9 ]/, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
# only allows numbers
|
||||||
|
def clean_number(str)
|
||||||
|
str.gsub(/[^0-9]/, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
# for b64 inputs; thoroughly cleans them
|
||||||
|
def clean_b64(str)
|
||||||
|
GDBase64.encode(GDBase64.decode_string(str))
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue