You should never use Url.Link()
or Url.Action()
to send something to a user without setting host yourself in my opinion. You are exposing them to a possible Host Header Attack -> Password Reset Poisoning
.
If the IIS has a binding to accept connections on 80/443 the host header can be changed and in turn affecting the Url.Link()
or Url.Action()
methods. If you look at the request I'm making below I'm connecting to http://hostheaderattack
but manipulating the host
header.
Proof of Concept (PoC):
Url.Link:
public class TestController : ApiController
{
public IHttpActionResult Get()
{
var callbackUrl = Url.Link("Default", new
{
Controller = "Home",
Action = "Index",
});
return Ok(callbackUrl);
}
}

Url.Action:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Title = $"Url Created: {Url.Action("Index", "Home", "", Request.Url.Scheme)}";
return View();
}
}

I have demonstrated it here as well:
https://security.stackexchange.com/questions/170755/host-header-attack-password-reset-poisoning-asp-net-web-api-2-hosted-as-az/170759#170759
Some more reading about host header attack:
https://www.acunetix.com/blog/articles/automated-detection-of-host-header-attacks/
What you should do is never trust a user request and construct the url with host manually.
Example with manual host name for:
Url.Action: Url.Action("Index", "Home", null, Request.Url.Scheme, "example.com")
For Url.Link it is a bit trickier but it can be done like this:
public class TestController : ApiController
{
// GET api/<controller>
public IHttpActionResult Get()
{
var callbackUrl = Url.Link("Default", new
{
Controller = "Home",
Action = "Index",
});
callbackUrl = ReplaceHost(callbackUrl, "example.com");
return Ok(callbackUrl);
}
private string ReplaceHost(string original, string newHostName)
{
var builder = new UriBuilder(original);
builder.Host = newHostName;
return builder.Uri.ToString();
}
}
Source for ReplaceHost method:
https://stackoverflow.com/a/479812/3850405