限制Rails

时间:2018-01-11 03:52:12

标签: ruby-on-rails ruby security redirect

我有一个Rails应用程序需要允许单独的应用程序将已登录的用户与签名的JWT一起定向到我们的应用程序,在我们的应用程序中为用户创建会话,然后将用户重定向到各种用户之一应用程序中的页面。 Open redirects are bad,所以我试图弄清楚是否有可接受的最佳做法(甚至是好的建议)来处理限制Rails中的重定向网址。

一个问题是该方法理想地适用于部署在子目录中的应用程序。也就是说,应该阻止重定向到同一服务器上但位于应用程序根目录之外的位置。但这并不重要。

我已经研究过的事情:

  1. 测试用户提供的URL以查看它是否与应用程序的某个路由匹配。不幸的是,我还没有找到一个很好的方法来做到这一点。 ActionDispatch::Routing::RouteSet有一个recognize_path方法,但它不是公共API的一部分,甚至不被Rails路由机制使用。 ActionDispatch::Journey::Router有一个recognize方法,它同样不会被Rails路由机制直接调用(虽然它具有非常相似的逻辑),也不是公共API的一部分。它有一个find_routes方法,路由机制使用 ,但它是private方法。
  2. 检查用户提供的URL是否以应用程序的域和路径开头。这不是太糟糕,但是子目录部署和目录遍历的可能性变得棘手(/../)。 URI(root_url).merge(params[:url])似乎解析了目录遍历,只需检查它是否以root_url开头,这样就可以了。但我注意到/%2E%2E/ merge将忽略/../但仍被Apache和Chrome视为import android.Manifest; import android.app.Activity; import android.content.Context; import android.os.Build; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.content.pm.PackageManager; import android.hardware.Camera; import android.hardware.Camera.Parameters; import android.view.View; import android.widget.Button; import android.widget.ImageButton; import android.widget.Toast; public class MainActivity extends AppCompatActivity { Button button; private Camera camera; private boolean isFlashOn; private boolean hasFlash; Parameters params; private Context mContext = MainActivity.this; private static final int REQUEST = 112; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ImageButton imgbutton = (ImageButton)findViewById(R.id.imageButton); hasFlash = getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH); checkPermission(); //getCamera(); imgbutton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isFlashOn) { turnOffFlash(); Toast.makeText(MainActivity.this, "Flash OFF!", Toast.LENGTH_SHORT).show(); imgbutton.setImageResource(R.drawable.light_off); } else { turnOnFlash(); Toast.makeText(MainActivity.this, "Flash ON!", Toast.LENGTH_SHORT).show(); imgbutton.setImageResource(R.drawable.light_on); } } }); } public void checkPermission() { if (Build.VERSION.SDK_INT >= 23) { String[] PERMISSIONS = {android.Manifest.permission.CAMERA}; if (!hasPermissions(mContext, PERMISSIONS)) { ActivityCompat.requestPermissions((Activity) mContext, PERMISSIONS, REQUEST); } else { getCamera() ; } } else { getCamera() ; } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case REQUEST: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { getCamera() ; } else { Toast.makeText(mContext, "The app was not allowed to access camera", Toast.LENGTH_LONG).show(); } } } } private static boolean hasPermissions(Context context, String... permissions) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context != null && permissions != null) { for (String permission : permissions) { if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { return false; } } } return true; } private void getCamera() { if (camera == null) { try { camera = Camera.open(); params = camera.getParameters(); }catch (Exception e) { } } } private void turnOnFlash() { if(!isFlashOn) { if(camera == null || params == null) { return; } params = camera.getParameters(); params.setFlashMode(Parameters.FLASH_MODE_TORCH); camera.setParameters(params); camera.startPreview(); isFlashOn = true; } } private void turnOffFlash() { if (isFlashOn) { if (camera == null || params == null) { return; } params = camera.getParameters(); params.setFlashMode(Parameters.FLASH_MODE_OFF); camera.setParameters(params); camera.stopPreview(); isFlashOn = false; } } @Override protected void onDestroy(){ super.onDestroy(); if(camera!=null){ camera.stopPreview(); camera.setPreviewCallback(null); camera.release(); camera = null; } } protected void onStop() { super.onStop(); } @Override protected void onResume() { super.onResume(); if(hasFlash) turnOnFlash(); } @Override protected void onPause() { super.onPause(); } } (至少)。我不确定是否还有其他更明显的边缘情况。
  3. 如果有更好的方法来处理这种情况,而不是将重定向网址作为用户提供的参数,我也很高兴听到这种情况。虽然通过这种方法访问Rails应用程序中的每个页面都不是必不可少的,但每次需要允许其他页面时我也不想付出很多努力。

1 个答案:

答案 0 :(得分:0)

经过一番研究后,我决定选择URI#merge选项,并使用额外的替代来处理百分比编码问题。有点像:

normalized_url = params[:url].gsub(/%2E/i, '.')
merged_url = URI(root_url).merge(normalized_url).to_s
merged_url.start_with?(root_url)

根据section 2.3 of RFC 3986,句点是"无保留的字符," "将替换未保留字符与其对应的百分比编码的US-ASCII八位字节不同的URI是等效的。"所以我有理由确定用句点替换%2E%2e

同一RFC的第6节提到通过以下方式规范化通用URI:

  • 规范化案例:"百分比编码三元组中的十六进制数字"应被视为不区分大小写
  • 解码未保留字符的百分比编码版本
  • 根据需要解析...

这涵盖了通用URI的情况,RFC 7230似乎没有专门为HTTP和HTTPS URI添加与此情况相关的任何内容。所以我觉得我正在讨论最可能出现的问题,但我无法确定是否有一些我曾经错过的客户或服务器的边缘情况。