| 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(); |