I would like to set my forum signature in a phpbb forum, because it contains a coupon code and needs to be updated from time to time as the coupon expires, eg. once a month. So I want to achieve this automatically, for example with a cron job. I want to use cURL command line application and Ruby, because I am already familiar to them. However phpbb applies some security measuers called form creation_time and form_token to avoid automatic form submissions. They are created this way:
$now = time();
$token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
$token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
So creation_time represent the time as an integer value (Time.now.to_i in Ruby) and the form_token is a 40 character digest hash, which is a function of 4 variable: the mentioned time value, some kind of salt (it is taken from the phpbb database, and is set when user logs in first time), form name (ucp I think) and the PHP session id.
My code looks like follows:
#!/usr/bin/ruby
coupon_code=ARGV[0]
signature="For 20% discount, use this coupon code when buying from example.com: #{coupon_code}"
#getting a PHP session id, called sid from the login page before logging in
ret=%x{curl -c ~/example-com-cookie.txt 'http://www.example.com/ucp.php?mode=login' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-US,en;q=0.8' -H 'User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Referer: http://www.example.com/' -H 'Connection: keep-alive' -H 'Cache-Control: max-age=0' --compressed -L}
matchdata=/\<input type\=\"hidden\" name\=\"sid\" value\=\"(.*?)\".*?\/\>/.match(ret.scrub)
if matchdata != nil then
sid=matchdata[1]
else
sid=""
end
puts sid
#login to the forum, and getting the cookie values, storing the in a cookie file (cookie jar)
ret=%x{curl 'http://www.example.com/ucp.php?mode=login&sid=#{sid}' -c ~/example-com-cookie.txt -H 'Origin: http://www.example.com' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: en-US,en;q=0.8' -H 'User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Cache-Control: max-age=0' -H 'Referer: http://www.example.com' -H 'Connection: keep-alive' --data 'username=myusername&password=mypassword&sid=#{sid}&redirect=index.php&login=Login' --compressed -L}
#getting form_token and creation_time hidden HTML field values
ret=%x{curl 'http://www.example.com/ucp.php?i=profile&mode=signature' -b ~/example-com-cookie.txt -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-US,en;q=0.8' -H 'User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Referer: http://www.example.com/ucp.php?i=profile&mode=signature' -H 'Connection: keep-alive' -H 'Cache-Control: max-age=0' --compressed -L}
matchdata=(/\<input type\=\"hidden\" name\=\"creation_time\" value\=\"(.*?)\".*?\/\>.*?\<input type\=\"hidden\" name\=\"form_token\" value\=\"(.*?)\".*?\/\>/m).match(ret.scrub)
puts matchdata.inspect
creation_time=matchdata[1]
#creation_time="1435022180"
form_token=matchdata[2]
#form_token="7f9d14cac39a5c5bcaf91682b3ff0410ea7ba6b8"#
puts creation_time
puts form_token
#modifying signature
ret=%x{curl 'http://www.example.com/ucp.php?i=profile&mode=signature' -b ~/example-com-cookie.txt -H 'Origin: http://www.example.com' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: en-US,en;q=0.8' -H 'User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Cache-Control: max-age=0' -H 'Referer: http://www.example.com/ucp.php?i=profile&mode=signature' -H 'Connection: keep-alive' --data 'addbbcode20=100&signature=#{CGI.escape(signature)}&submit=Submit&creation_time=#{creation_time}&form_token=#{form_token}' --compressed -L}
matchdata=/\<span class\=\"genmed error\"\>(.*?)\<\/span\>/.match(ret.scrub)
if matchdata == nil then
puts "All OK!"
else
puts matchdata[1]
end
I took the cURL lines form a real browser session made with Chrome, I simply copied the POST and GET requests from the developer console (Ctrl+Shift+I) with context menu "Copy as cURL" and then I made the necessary substitutions. Unfortunately my code doesn't work. I always get a HTML page containing the error message "Invalid Form".
However when I use the two commented lines when setting the creation_time and form_token variables (they originate from a real browser session, I simply copied them from the HTML source)
creation_time="1435022180"
form_token="7f9d14cac39a5c5bcaf91682b3ff0410ea7ba6b8"
my script works again even if I logout and login again from and into that real browser session! What's going on here?
phpBB3 checks the CSRF tokens for immediate requests following and assumes bots: https://github.com/phpbb/phpbb/blob/master/phpBB/includes/functions.php#L2102