Index: trunk/phase3/includes/User.php |
— | — | @@ -1187,6 +1187,39 @@ |
1188 | 1188 | } |
1189 | 1189 | return false; |
1190 | 1190 | } |
| 1191 | + |
| 1192 | + /** |
| 1193 | + * Initialize (if necessary) and return a session token value |
| 1194 | + * which can be used in edit forms to show that the user's |
| 1195 | + * login credentials aren't being hijacked with a foreign form |
| 1196 | + * submission. |
| 1197 | + * |
| 1198 | + * @return string |
| 1199 | + * @access public |
| 1200 | + */ |
| 1201 | + function editToken() { |
| 1202 | + if( !isset( $_SESSION['wsEditToken'] ) ) { |
| 1203 | + $token = dechex( mt_rand() ) . dechex( mt_rand() ); |
| 1204 | + $_SESSION['wsEditToken'] = $token; |
| 1205 | + } |
| 1206 | + return $_SESSION['wsEditToken']; |
| 1207 | + } |
| 1208 | + |
| 1209 | + /** |
| 1210 | + * Check given value against the token value stored in the session. |
| 1211 | + * A match should confirm that the form was submitted from the |
| 1212 | + * user's own login session, not a form submission from a third-party |
| 1213 | + * site. |
| 1214 | + * |
| 1215 | + * @param string $val |
| 1216 | + * @return bool |
| 1217 | + * @access public |
| 1218 | + */ |
| 1219 | + function matchEditToken( $val ) { |
| 1220 | + if( !isset( $_SESSION['wsEditToken'] ) ) |
| 1221 | + return false; |
| 1222 | + return $_SESSION['wsEditToken'] == $val; |
| 1223 | + } |
1191 | 1224 | } |
1192 | 1225 | |
1193 | 1226 | ?> |
Index: trunk/phase3/includes/EditPage.php |
— | — | @@ -198,11 +198,17 @@ |
199 | 199 | # If the form is incomplete, force to preview. |
200 | 200 | $this->preview = true; |
201 | 201 | } else { |
202 | | - # Some browsers will not report any submit button |
203 | | - # if the user hits enter in the comment box. |
204 | | - # The unmarked state will be assumed to be a save, |
205 | | - # if the form seems otherwise complete. |
206 | | - $this->preview = $request->getCheck( 'wpPreview' ); |
| 202 | + if( $this->tokenOk( $request ) ) { |
| 203 | + # Some browsers will not report any submit button |
| 204 | + # if the user hits enter in the comment box. |
| 205 | + # The unmarked state will be assumed to be a save, |
| 206 | + # if the form seems otherwise complete. |
| 207 | + $this->preview = $request->getCheck( 'wpPreview' ); |
| 208 | + } else { |
| 209 | + # Page might be a hack attempt posted from |
| 210 | + # an external site. Preview instead of saving. |
| 211 | + $this->preview = true; |
| 212 | + } |
207 | 213 | } |
208 | 214 | $this->save = !$this->preview; |
209 | 215 | if( !preg_match( '/^\d{14}$/', $this->edittime )) { |
— | — | @@ -232,6 +238,24 @@ |
233 | 239 | $this->live = $request->getCheck( 'live' ); |
234 | 240 | } |
235 | 241 | |
| 242 | + /** |
| 243 | + * Make sure the form isn't faking a user's credentials. |
| 244 | + * |
| 245 | + * @param WebRequest $request |
| 246 | + * @return bool |
| 247 | + * @access private |
| 248 | + */ |
| 249 | + function tokenOk( &$request ) { |
| 250 | + global $wgUser; |
| 251 | + if( $wgUser->getId() == 0 ) { |
| 252 | + # Anonymous users may not have a session |
| 253 | + # open. Don't tokenize. |
| 254 | + return true; |
| 255 | + } else { |
| 256 | + return $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); |
| 257 | + } |
| 258 | + } |
| 259 | + |
236 | 260 | function submit() { |
237 | 261 | $this->edit(); |
238 | 262 | } |
— | — | @@ -632,6 +656,21 @@ |
633 | 657 | <input type='hidden' value=\"" . htmlspecialchars( $this->section ) . "\" name=\"wpSection\" /> |
634 | 658 | <input type='hidden' value=\"{$this->edittime}\" name=\"wpEdittime\" />\n" ); |
635 | 659 | |
| 660 | + if ( 0 != $wgUser->getID() ) { |
| 661 | + /** |
| 662 | + * To make it harder for someone to slip a user a page |
| 663 | + * which submits an edit form to the wiki without their |
| 664 | + * knowledge, a random token is associated with the login |
| 665 | + * session. If it's not passed back with the submission, |
| 666 | + * we won't save the page, or render user JavaScript and |
| 667 | + * CSS previews. |
| 668 | + */ |
| 669 | + $token = htmlspecialchars( $wgUser->editToken() ); |
| 670 | + $wgOut->addHTML( " |
| 671 | +<input type='hidden' value=\"$token\" name=\"wpEditToken\" />\n" ); |
| 672 | + } |
| 673 | + |
| 674 | + |
636 | 675 | if ( $isConflict ) { |
637 | 676 | require_once( "DifferenceEngine.php" ); |
638 | 677 | $wgOut->addHTML( "<h2>" . wfMsg( "yourdiff" ) . "</h2>\n" ); |
Index: trunk/phase3/includes/SkinTemplate.php |
— | — | @@ -825,7 +825,7 @@ |
826 | 826 | $action = $wgRequest->getText('action'); |
827 | 827 | |
828 | 828 | # if we're previewing the CSS page, use it |
829 | | - if($wgTitle->isCssSubpage() and $action == 'submit' and $wgTitle->userCanEditCssJsSubpage()) { |
| 829 | + if( $wgTitle->isCssSubpage() and $this->userCanPreview( $action ) ) { |
830 | 830 | $siteargs = "&smaxage=0&maxage=0"; |
831 | 831 | $usercss = $wgRequest->getText('wpTextbox1'); |
832 | 832 | } else { |
— | — | @@ -864,7 +864,7 @@ |
865 | 865 | $action = $wgRequest->getText('action'); |
866 | 866 | |
867 | 867 | if( $wgAllowUserJs && $this->loggedin ) { |
868 | | - if($wgTitle->isJsSubpage() and $action == 'submit' and $wgTitle->userCanEditCssJsSubpage()) { |
| 868 | + if( $wgTitle->isJsSubpage() and $this->userCanPreview( $action ) ) { |
869 | 869 | # XXX: additional security check/prompt? |
870 | 870 | $this->userjsprev = '/*<![CDATA[*/ ' . $wgRequest->getText('wpTextbox1') . ' /*]]>*/'; |
871 | 871 | } else { |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -697,7 +697,7 @@ |
698 | 698 | * This is the list of preferred extensions for uploading files. Uploading files |
699 | 699 | * with extensions not in this list will trigger a warning. |
700 | 700 | */ |
701 | | -$wgFileExtensions = array( 'png', 'gif', 'jpg', 'jpeg', 'ogg' ); |
| 701 | +$wgFileExtensions = array( 'png', 'gif', 'jpg', 'jpeg' ); |
702 | 702 | |
703 | 703 | /** Files with these extensions will never be allowed as uploads. */ |
704 | 704 | $wgFileBlacklist = array( |
— | — | @@ -912,11 +912,19 @@ |
913 | 913 | $wgSkinExtensionFunctions = array(); |
914 | 914 | $wgExtensionFunctions = array(); |
915 | 915 | |
916 | | -/** Allow user Javascript page? */ |
917 | | -$wgAllowUserJs = true; |
| 916 | +/** |
| 917 | + * Allow user Javascript page? |
| 918 | + * This enables a lot of neat customizations, but may |
| 919 | + * increase security risk to users and server load. |
| 920 | + */ |
| 921 | +$wgAllowUserJs = false; |
918 | 922 | |
919 | | -/** Allow user Cascading Style Sheets (CSS)? */ |
920 | | -$wgAllowUserCss = true; |
| 923 | +/** |
| 924 | + * Allow user Cascading Style Sheets (CSS)? |
| 925 | + * This enables a lot of neat customizations, but may |
| 926 | + * increase security risk to users and server load. |
| 927 | + */ |
| 928 | +$wgAllowUserCss = false; |
921 | 929 | |
922 | 930 | /** Use the site's Javascript page? */ |
923 | 931 | $wgUseSiteJs = true; |
Index: trunk/phase3/includes/Skin.php |
— | — | @@ -194,6 +194,30 @@ |
195 | 195 | return $r; |
196 | 196 | } |
197 | 197 | |
| 198 | + /** |
| 199 | + * To make it harder for someone to slip a user a fake |
| 200 | + * user-JavaScript or user-CSS preview, a random token |
| 201 | + * is associated with the login session. If it's not |
| 202 | + * passed back with the preview request, we won't render |
| 203 | + * the code. |
| 204 | + * |
| 205 | + * @param string $action |
| 206 | + * @return bool |
| 207 | + * @access private |
| 208 | + */ |
| 209 | + function userCanPreview( $action ) { |
| 210 | + global $wgTitle, $wgRequest, $wgUser; |
| 211 | + |
| 212 | + if( $action != 'submit' ) |
| 213 | + return false; |
| 214 | + if( !$wgRequest->wasPosted() ) |
| 215 | + return false; |
| 216 | + if( !$wgTitle->userCanEditCssJsSubpage() ) |
| 217 | + return false; |
| 218 | + return $wgUser->matchEditToken( |
| 219 | + $wgRequest->getVal( 'wpEditToken' ) ); |
| 220 | + } |
| 221 | + |
198 | 222 | # get the user/site-specific stylesheet, SkinPHPTal called from RawPage.php (settings are cached that way) |
199 | 223 | function getUserStylesheet() { |
200 | 224 | global $wgOut, $wgStylePath, $wgContLang, $wgUser, $wgRequest, $wgTitle, $wgAllowUserCss; |
— | — | @@ -202,7 +226,7 @@ |
203 | 227 | $s = "@import \"$wgStylePath/$sheet\";\n"; |
204 | 228 | if($wgContLang->isRTL()) $s .= "@import \"$wgStylePath/common/common_rtl.css\";\n"; |
205 | 229 | if( $wgAllowUserCss && $wgUser->getID() != 0 ) { # logged in |
206 | | - if($wgTitle->isCssSubpage() and $action == 'submit' and $wgTitle->userCanEditCssJsSubpage()) { |
| 230 | + if($wgTitle->isCssSubpage() && $this->userCanPreview( $action ) ) { |
207 | 231 | $s .= $wgRequest->getText('wpTextbox1'); |
208 | 232 | } else { |
209 | 233 | $userpage = $wgContLang->getNsText( Namespace::getUser() ) . ":" . $wgUser->getName(); |