1’;rename table words to n0p3;rename table `1919810931114514` to words;alter table words add id int unsigned not Null auto_increment primary key; alter table words change flag data varchar(100);#
1
1‘;rename tables `words` to `n0p3`;rename tables `1919810931114514` to `words`; alter table `words` change `flag` `id` varchar(100);
signed __int64 __fastcall input_BUG(_BYTE *a1, int _32) { int i; // [rsp+14h] [rbp-Ch] _BYTE *_addr; // [rsp+18h] [rbp-8h]
if ( _32 <= 0 ) return0LL; _addr = a1; for ( i = 0; ; ++i ) { if ( (unsignedint)read(0, _addr, 1uLL) != 1 ) return1LL; if ( *_addr == '\n' ) break; ++_addr; if ( i == _32 ) // 等于a2就跳出 break; } *_addr = 0; return0LL; }
这个开发者自定义的输入函数被我标记了BUG,是因为它在line 16处确实存在一个漏洞,在line 9可以看到计数器i从0开始,当等于32时结束。但这个if判断在循环体下方执行,也就是说判断到i==32时其实又执行了一次循环体,实际上我们这个循环执行了33次。我们可以比开发者设想的多写入一个字节,也就是Off by one。(当然有可能开发者本就想让用户输入33个字节,稍后我们会知道我们确实超出了开发者预期)
AssignBookNumber()
在CreateBook()函数的line 39处有一个AssignBookNumber()
1 2 3 4 5 6 7 8 9 10 11
signed __int64 AssignBookNumber() { signedint i; // [rsp+0h] [rbp-4h]
for ( i = 0; i <= 19; ++i ) // 实际执行20次,最多存20本书 { if ( !*((_QWORD *)BooksZone + i) ) // 似乎可以访问到off_202018的数据 return (unsignedint)i; } return0xFFFFFFFFLL; }
# don't minify HTML Slim::Engine.set_options pretty:true
configure do set :public_folder, 'public' set :views, 'views' set :bind, '0.0.0.0' set :port, 9999 enable :sessions set :server, %w[thin webrick] set :environment, :production #set :environment, :development #disable :protection set :session_secret, '01344904559362f6f5754df256908476702c8bd5d972a32e2fae2a7cc6fa4a7efd25079fddb5a11a0f8be0f607bf048fd6ecfe065380c27b2aa26015c3308e85' end
defauthenticate! unless session[:username] redirect to('/login') end end
get '/login'do @message = params['message'] @hide = 'hidden'if params['message'].nil? slim :login end
post '/auth'do # Open a database db = SQLite3::Database.new 'database.db', {readonly:true} user = params['username'] pass = params['password'] query = "SELECT username, password, role FROM users WHERE username='#{user}' AND password='#{pass}';" rows = db.execute(query) db.close unless rows.empty? username, password, role = rows.first session.clear session[:username] = username session[:role] = role redirect to('/home') else sleep 0.1 message = '错误的凭证!' redirect to("/login?message=#{message}") end end
post '/register'do sleep 0.1 message = '不可注册!' redirect to("/login?message=#{message}") end
get '/home'do authenticate! @user = session[:username] @flag = ENV['FLAG'] if session[:role] == 'admin' slim :home end
get '/logout'do session.clear message = '你已经退出登录啦.' redirect to("/login?message=#{message}") end
post '/id'do authenticate! unless params['message'].nil? id = params['message'].unpack('H*').first redirect to("/id/#{id}") else redirect to('/home') end end
# Bad code get '/id/:id'do authenticate! message = [params['id']].pack('H*') # Filesystem is mounted in readonly so RCE is not so much a problem # but I prefer people to the the secret with the elegant solution # Sinatra::Application.settings.session_secret # rather than by reading the code source. So let's put a little, certainly not # unbreakable, blacklist. blacklist_methods = /`|exec|foreach|fork|load|method_added|open|read(?!line$)|require|set_trace_func|spawn|syscall|system|base64|pack/ blacklist_classes = /(?<!True|False|Nil)Class|Module|Dir|File|ObjectSpace|Process|Thread|IO/ blacklist_chars = /\(|\)|\[|\]/ if blacklist_methods.match?(message) || blacklist_classes.match?(message) || blacklist_chars.match?(message) message = '不对哦, 在试试吧!.' end # SSTI template = File.read('views/id.slim').gsub('@@@REPLACE@@@', message) Slim::Template.new{ template }.render end
=begin # Good code get '/id/:id' do authenticate! @message = [params['id']].pack('H*') slim :index end =end