| 
									
										
										
										
											2012-04-12 13:09:04 -07:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-05 14:53:24 -08:00
										 |  |  | final class PhamePostViewController extends PhamePostController { | 
					
						
							| 
									
										
										
										
											2012-04-12 13:09:04 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-15 13:07:45 -07:00
										 |  |  |   public function handleRequest(AphrontRequest $request) { | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |     $viewer = $request->getViewer(); | 
					
						
							| 
									
										
										
										
											2012-04-12 13:09:04 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |     $post = id(new PhamePostQuery()) | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |       ->setViewer($viewer) | 
					
						
							| 
									
										
										
										
											2015-05-15 13:07:45 -07:00
										 |  |  |       ->withIDs(array($request->getURIData('id'))) | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |       ->executeOne(); | 
					
						
							| 
									
										
										
										
											2012-04-12 13:09:04 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!$post) { | 
					
						
							|  |  |  |       return new Aphront404Response(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-04-14 08:02:29 -07:00
										 |  |  |     $crumbs = $this->buildApplicationCrumbs(); | 
					
						
							| 
									
										
										
										
											2013-12-18 17:47:34 -08:00
										 |  |  |     $crumbs->addTextCrumb( | 
					
						
							|  |  |  |       $post->getTitle(), | 
					
						
							|  |  |  |       $this->getApplicationURI('post/view/'.$post->getID().'/')); | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |     $crumbs->setBorder(true); | 
					
						
							| 
									
										
										
										
											2013-04-14 08:02:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |     $actions = $this->renderActions($post, $viewer); | 
					
						
							|  |  |  |     $properties = $this->renderProperties($post, $viewer); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $action_button = id(new PHUIButtonView()) | 
					
						
							|  |  |  |       ->setTag('a') | 
					
						
							|  |  |  |       ->setText(pht('Actions')) | 
					
						
							|  |  |  |       ->setHref('#') | 
					
						
							|  |  |  |       ->setIconFont('fa-bars') | 
					
						
							|  |  |  |       ->addClass('phui-mobile-menu') | 
					
						
							|  |  |  |       ->setDropdownMenu($actions); | 
					
						
							| 
									
										
										
										
											2013-09-28 15:55:38 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     $header = id(new PHUIHeaderView()) | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |       ->setHeader($post->getTitle()) | 
					
						
							|  |  |  |       ->setUser($viewer) | 
					
						
							|  |  |  |       ->setPolicyObject($post) | 
					
						
							|  |  |  |       ->addActionLink($action_button); | 
					
						
							| 
									
										
										
										
											2013-09-28 15:55:38 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |     $document = id(new PHUIDocumentViewPro()) | 
					
						
							| 
									
										
										
										
											2013-09-28 15:55:38 -07:00
										 |  |  |       ->setHeader($header) | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |       ->setPropertyList($properties); | 
					
						
							| 
									
										
										
										
											2012-10-16 09:44:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-12 13:09:04 -07:00
										 |  |  |     if ($post->isDraft()) { | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |       $document->appendChild( | 
					
						
							| 
									
										
										
										
											2015-03-01 14:45:56 -08:00
										 |  |  |         id(new PHUIInfoView()) | 
					
						
							|  |  |  |           ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) | 
					
						
							| 
									
										
										
										
											2012-10-16 09:44:43 -07:00
										 |  |  |           ->setTitle(pht('Draft Post')) | 
					
						
							|  |  |  |           ->appendChild( | 
					
						
							| 
									
										
										
										
											2015-05-22 17:27:56 +10:00
										 |  |  |             pht( | 
					
						
							|  |  |  |               'Only you can see this draft until you publish it. '. | 
					
						
							|  |  |  |               'Use "Preview / Publish" to publish this post.'))); | 
					
						
							| 
									
										
										
										
											2012-10-16 09:44:43 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!$post->getBlog()) { | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |       $document->appendChild( | 
					
						
							| 
									
										
										
										
											2015-03-01 14:45:56 -08:00
										 |  |  |         id(new PHUIInfoView()) | 
					
						
							|  |  |  |           ->setSeverity(PHUIInfoView::SEVERITY_WARNING) | 
					
						
							| 
									
										
										
										
											2012-10-16 09:44:43 -07:00
										 |  |  |           ->setTitle(pht('Not On A Blog')) | 
					
						
							|  |  |  |           ->appendChild( | 
					
						
							| 
									
										
										
										
											2015-05-22 17:27:56 +10:00
										 |  |  |             pht( | 
					
						
							|  |  |  |               'This post is not associated with a blog (the blog may have '. | 
					
						
							|  |  |  |               'been deleted). Use "Move Post" to move it to a new blog.'))); | 
					
						
							| 
									
										
										
										
											2012-04-12 13:09:04 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |     $engine = id(new PhabricatorMarkupEngine()) | 
					
						
							|  |  |  |       ->setViewer($viewer) | 
					
						
							|  |  |  |       ->addObject($post, PhamePost::MARKUP_FIELD_BODY) | 
					
						
							|  |  |  |       ->process(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $document->appendChild( | 
					
						
							|  |  |  |       phutil_tag( | 
					
						
							|  |  |  |          'div', | 
					
						
							|  |  |  |         array( | 
					
						
							|  |  |  |           'class' => 'phabricator-remarkup', | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         $engine->getOutput($post, PhamePost::MARKUP_FIELD_BODY))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return $this->newPage() | 
					
						
							|  |  |  |       ->setTitle($post->getTitle()) | 
					
						
							|  |  |  |       ->addClass('pro-white-background') | 
					
						
							|  |  |  |       ->setCrumbs($crumbs) | 
					
						
							|  |  |  |       ->appendChild( | 
					
						
							|  |  |  |         array( | 
					
						
							|  |  |  |           $document, | 
					
						
							| 
									
										
										
										
											2012-04-12 13:09:04 -07:00
										 |  |  |       )); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   private function renderActions( | 
					
						
							|  |  |  |     PhamePost $post, | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |     PhabricatorUser $viewer) { | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-15 13:07:45 -07:00
										 |  |  |       $actions = id(new PhabricatorActionListView()) | 
					
						
							|  |  |  |         ->setObject($post) | 
					
						
							|  |  |  |         ->setObjectURI($this->getRequest()->getRequestURI()) | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |         ->setUser($viewer); | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     $can_edit = PhabricatorPolicyFilter::hasCapability( | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |       $viewer, | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |       $post, | 
					
						
							|  |  |  |       PhabricatorPolicyCapability::CAN_EDIT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $id = $post->getID(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $actions->addAction( | 
					
						
							|  |  |  |       id(new PhabricatorActionView()) | 
					
						
							| 
									
										
										
										
											2014-05-12 10:08:32 -07:00
										 |  |  |         ->setIcon('fa-pencil') | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |         ->setHref($this->getApplicationURI('post/edit/'.$id.'/')) | 
					
						
							| 
									
										
										
										
											2013-04-14 08:02:29 -07:00
										 |  |  |         ->setName(pht('Edit Post')) | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |         ->setDisabled(!$can_edit) | 
					
						
							|  |  |  |         ->setWorkflow(!$can_edit)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $actions->addAction( | 
					
						
							|  |  |  |       id(new PhabricatorActionView()) | 
					
						
							| 
									
										
										
										
											2014-05-12 10:08:32 -07:00
										 |  |  |         ->setIcon('fa-arrows') | 
					
						
							| 
									
										
										
										
											2012-10-16 09:44:43 -07:00
										 |  |  |         ->setHref($this->getApplicationURI('post/move/'.$id.'/')) | 
					
						
							| 
									
										
										
										
											2013-04-14 08:02:29 -07:00
										 |  |  |         ->setName(pht('Move Post')) | 
					
						
							| 
									
										
										
										
											2012-10-16 09:44:43 -07:00
										 |  |  |         ->setDisabled(!$can_edit) | 
					
						
							|  |  |  |         ->setWorkflow(!$can_edit)); | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if ($post->isDraft()) { | 
					
						
							|  |  |  |       $actions->addAction( | 
					
						
							|  |  |  |         id(new PhabricatorActionView()) | 
					
						
							| 
									
										
										
										
											2014-05-13 07:45:39 -07:00
										 |  |  |           ->setIcon('fa-eye') | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |           ->setHref($this->getApplicationURI('post/publish/'.$id.'/')) | 
					
						
							| 
									
										
										
										
											2015-11-09 08:52:44 -08:00
										 |  |  |           ->setDisabled(!$can_edit) | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |           ->setName(pht('Preview / Publish'))); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       $actions->addAction( | 
					
						
							|  |  |  |         id(new PhabricatorActionView()) | 
					
						
							| 
									
										
										
										
											2014-05-13 07:45:39 -07:00
										 |  |  |           ->setIcon('fa-eye-slash') | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |           ->setHref($this->getApplicationURI('post/unpublish/'.$id.'/')) | 
					
						
							|  |  |  |           ->setName(pht('Unpublish')) | 
					
						
							| 
									
										
										
										
											2015-11-09 08:52:44 -08:00
										 |  |  |           ->setDisabled(!$can_edit) | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |           ->setWorkflow(true)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $actions->addAction( | 
					
						
							|  |  |  |       id(new PhabricatorActionView()) | 
					
						
							| 
									
										
										
										
											2014-05-13 07:45:39 -07:00
										 |  |  |         ->setIcon('fa-times') | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |         ->setHref($this->getApplicationURI('post/delete/'.$id.'/')) | 
					
						
							| 
									
										
										
										
											2013-04-14 08:02:29 -07:00
										 |  |  |         ->setName(pht('Delete Post')) | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |         ->setDisabled(!$can_edit) | 
					
						
							|  |  |  |         ->setWorkflow(true)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-16 09:44:43 -07:00
										 |  |  |     $blog = $post->getBlog(); | 
					
						
							|  |  |  |     $can_view_live = $blog && !$post->isDraft(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ($can_view_live) { | 
					
						
							| 
									
										
											  
											
												Don't 302 to an external URI, even after CSRF POST
Summary:
Via HackerOne. This defuses an attack which allows users to steal OAuth tokens through a clever sequence of steps:
  - The attacker begins the OAuth workflow and copies the Facebook URL.
  - The attacker mutates the URL to use the JS/anchor workflow, and to redirect to `/phame/live/X/` instead of `/login/facebook:facebook.com/`, where `X` is the ID of some blog they control. Facebook isn't strict about paths, so this is allowed.
  - The blog has an external domain set (`blog.evil.com`), and the attacker controls that domain.
  - The user gets stopped on the "live" controller with credentials in the page anchor (`#access_token=...`) and a message ("This blog has moved...") in a dialog. They click "Continue", which POSTs a CSRF token.
  - When a user POSTs a `<form />` with no `action` attribute, the browser retains the page anchor. So visiting `/phame/live/8/#anchor` and clicking the "Continue" button POSTs you to a page with `#anchor` intact.
  - Some browsers (including Firefox and Chrome) retain the anchor after a 302 redirect.
  - The OAuth credentials are thus preserved when the user reaches `blog.evil.com`, and the attacker's site can read them.
This 302'ing after CSRF post is unusual in Phabricator and unique to Phame. It's not necessary -- instead, just use normal links, which drop anchors.
I'm going to pursue further steps to mitigate this class of attack more thoroughly:
  - Ideally, we should render forms with an explicit `action` attribute, but this might be a lot of work. I might render them with `#` if no action is provided. We never expect anchors to survive POST, and it's surprising to me that they do.
  - I'm going to blacklist OAuth parameters (like `access_token`) from appearing in GET on all pages except whitelisted pages (login pages). Although it's not important here, I think these could be captured from referrers in some cases. See also T4342.
Test Plan: Browsed all the affected Phame interfaces.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, arice
Differential Revision: https://secure.phabricator.com/D8481
											
										 
											2014-03-10 16:21:07 -07:00
										 |  |  |       $live_uri = $blog->getLiveURI($post); | 
					
						
							| 
									
										
										
										
											2012-10-16 09:44:43 -07:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       $live_uri = 'post/notlive/'.$post->getID().'/'; | 
					
						
							| 
									
										
											  
											
												Don't 302 to an external URI, even after CSRF POST
Summary:
Via HackerOne. This defuses an attack which allows users to steal OAuth tokens through a clever sequence of steps:
  - The attacker begins the OAuth workflow and copies the Facebook URL.
  - The attacker mutates the URL to use the JS/anchor workflow, and to redirect to `/phame/live/X/` instead of `/login/facebook:facebook.com/`, where `X` is the ID of some blog they control. Facebook isn't strict about paths, so this is allowed.
  - The blog has an external domain set (`blog.evil.com`), and the attacker controls that domain.
  - The user gets stopped on the "live" controller with credentials in the page anchor (`#access_token=...`) and a message ("This blog has moved...") in a dialog. They click "Continue", which POSTs a CSRF token.
  - When a user POSTs a `<form />` with no `action` attribute, the browser retains the page anchor. So visiting `/phame/live/8/#anchor` and clicking the "Continue" button POSTs you to a page with `#anchor` intact.
  - Some browsers (including Firefox and Chrome) retain the anchor after a 302 redirect.
  - The OAuth credentials are thus preserved when the user reaches `blog.evil.com`, and the attacker's site can read them.
This 302'ing after CSRF post is unusual in Phabricator and unique to Phame. It's not necessary -- instead, just use normal links, which drop anchors.
I'm going to pursue further steps to mitigate this class of attack more thoroughly:
  - Ideally, we should render forms with an explicit `action` attribute, but this might be a lot of work. I might render them with `#` if no action is provided. We never expect anchors to survive POST, and it's surprising to me that they do.
  - I'm going to blacklist OAuth parameters (like `access_token`) from appearing in GET on all pages except whitelisted pages (login pages). Although it's not important here, I think these could be captured from referrers in some cases. See also T4342.
Test Plan: Browsed all the affected Phame interfaces.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, arice
Differential Revision: https://secure.phabricator.com/D8481
											
										 
											2014-03-10 16:21:07 -07:00
										 |  |  |       $live_uri = $this->getApplicationURI($live_uri); | 
					
						
							| 
									
										
										
										
											2012-10-16 09:44:43 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $actions->addAction( | 
					
						
							|  |  |  |       id(new PhabricatorActionView()) | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |         ->setUser($viewer) | 
					
						
							| 
									
										
										
										
											2014-05-13 07:45:39 -07:00
										 |  |  |         ->setIcon('fa-globe') | 
					
						
							| 
									
										
										
										
											2012-10-16 09:44:43 -07:00
										 |  |  |         ->setHref($live_uri) | 
					
						
							|  |  |  |         ->setName(pht('View Live')) | 
					
						
							|  |  |  |         ->setDisabled(!$can_view_live) | 
					
						
							|  |  |  |         ->setWorkflow(!$can_view_live)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |     return $actions; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private function renderProperties( | 
					
						
							|  |  |  |     PhamePost $post, | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |     PhabricatorUser $viewer) { | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-11 07:53:56 -07:00
										 |  |  |     $properties = id(new PHUIPropertyListView()) | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |       ->setUser($viewer) | 
					
						
							|  |  |  |       ->setObject($post); | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     $properties->addProperty( | 
					
						
							|  |  |  |       pht('Blog'), | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |       $viewer->renderHandle($post->getBlogPHID())); | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     $properties->addProperty( | 
					
						
							|  |  |  |       pht('Blogger'), | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |       $viewer->renderHandle($post->getBloggerPHID())); | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     $properties->addProperty( | 
					
						
							|  |  |  |       pht('Published'), | 
					
						
							|  |  |  |       $post->isDraft() | 
					
						
							|  |  |  |         ? pht('Draft') | 
					
						
							| 
									
										
										
										
											2015-11-05 12:13:35 -08:00
										 |  |  |         : phabricator_datetime($post->getDatePublished(), $viewer)); | 
					
						
							| 
									
										
										
										
											2012-10-15 14:51:04 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-05-09 14:40:43 -07:00
										 |  |  |     $properties->invokeWillRenderEvent(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-15 14:50:45 -07:00
										 |  |  |     return $properties; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2014-07-10 08:12:48 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-12 13:09:04 -07:00
										 |  |  | } |