Answer the question
In order to leave comments, you need to log in
How to properly handle iOS CNA when connecting to WiFI with Captive Portal?
Good day!
Task: Authorization of WiFi users is required. On iOS, you need to open the captive portal not in CNA, but in Safari. Actually, I did it ... almost.
iOS, when connected to WiFi, makes a certain request, expecting to receive a certain response. If this response is faked, then iOS will assume that the internet is open. But then CNA will not appear, and this is bad.
Idea such:
1. We do not forge the first request. We return HTML that contains a link, such as click me . The CNA (smaaaaaaaaaaaaaaly limited browser) will open;
2. We fake the rest of the requests, as if apple itself answers.
It partially worked. If you click on the link from step 1, CNA will close and the link will start opening in Safari. It seems like a time to rejoice, but shish! The request goes through the mobile network, and iOS silently disconnects from the WiFi network.
I understand the task now as follows: when CNA is already on the screen, make iOS think that Internet access via this WiFi is already open. Then iOS will not disconnect from WiFi and the request in the browser will already go correctly and there will be happiness! How to do this, until I caught up (
The solution clearly exists. They did it in the Moscow metro.
People, help! There is no way to ride the metro and sniff traffic ((
PS Standard CNA is not suitable, because it closes and disconnects from WiFi for In my case, the user sometimes needs to go to the SMS application and then return to the
portal.UPD. :CNA has a "Done" button. If you click it, instead of a link, then iOS decides that there is Internet access. Probably, you need to somehow simulate pressing this button when clicking on the link
Answer the question
In order to leave comments, you need to log in
Watch your hands:
21:23:25 GET "captive.apple.com" /hotspot-detect.html HTTP/1.0 302 0 - "CaptiveNetworkSupport-346 wispr" - - - "http://captive.apple.com/hotspot-1.html"
21:23:25 GET "captive.apple.com" /hotspot-1.html HTTP/1.0 403 86 - "CaptiveNetworkSupport-346 wispr" - - - -
21:23:26 GET "captive.apple.com" /hotspot-detect.html HTTP/1.1 200 467 - "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2 like Mac OS X) AppleWebKit/602.3.12 (KHTML, like Gecko) Mobile/14C92" - - - -
21:23:26 GET "captive.apple.com" /hotspot-detect.html HTTP/1.0 200 68 - "CaptiveNetworkSupport-346 wispr" - - - -
21:23:28 GET "captive.apple.com" /hotspot-2.html HTTP/1.1 302 0 - "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2 like Mac OS X) AppleWebKit/602.3.12 (KHTML, like Gecko) Version/10.0 Mobile/14C92 Safari/602.1" - - - "http://мой-URL"
with AWS.Status;
with AWS.Response;
package Worker_Echoes.Apple.Captive is
function Service (Request : AWS.Status.Data)
return AWS.Response.Data;
end Worker_Echoes.Apple.Captive;
with Ada.Strings.Fixed;
with AWS.URL;
with AWS.Messages;
with Worker_Echoes.Protected_Strings;
with Worker_Echoes.Config;
package body Worker_Echoes.Apple.Captive is
use AWS;
use all type AWS.Messages.Status_Code;
Last_CNA_CNS_Peername : Protected_Strings.Protected_String;
-------------
-- Service --
-------------
function Service (Request : Status.Data) return Response.Data is
URL_Object : constant URL.Object := Status.URI (Request);
URL_String : constant String := URL.URL (URL_Object);
User_Agent : constant String := Status.User_Agent (Request);
begin
if User_Agent'Length >= 6 and then User_Agent (User_Agent'Last - 5 .. User_Agent'Last) = " wispr" then
if Last_CNA_CNS_Peername.Get = Status.Peername (Request) then
return Response.Build
(Status_Code => S200,
Content_Type => "text/html; charset=utf-8",
Message_Body => "<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>");
elsif URL_String = "http://captive.apple.com/hotspot-1.html" then
return Response.Build
(Status_Code => S200,
Content_Type => "text/html; charset=utf-8",
Message_Body => "<HTML><HEAD></HEAD><BODY></BODY></HTML>");
else
return Response.URL ("http://captive.apple.com/hotspot-1.html");
end if;
elsif Ada.Strings.Fixed.Index (User_Agent, "Safari") = 0 then
Last_CNA_CNS_Peername.Set (Status.Peername (Request));
-- 1. Not sure if intact "<BODY>Success</BODY>" matters, but probably yes.
-- 2. There was no delay previously, thus A had to be created before BODY is processed.
-- Now it's possible to click A with id, but touching working code was avoided.
return Response.Build
(Status_Code => S200,
Content_Type => "text/html; charset=utf-8",
Message_Body => "<HTML><HEAD><SCRIPT>" &
"window.setTimeout (function () {" &
"var A = document.createElement (""a"");" &
"A.setAttribute (""href"", ""http://captive.apple.com/hotspot-2.html"");" &
"var Body = document.getElementsByTagName (""body"");" &
"if (Body.length > 0) {" &
"Body = Body [0];" &
"} else {" &
"Body = document.createElement (""body"");" &
"document.getElementsByTagName (""html"") [0].appendChild (Body);" &
"}" &
"Body.appendChild (A);" &
"A.click ();" &
"}, 1000);" &
"</SCRIPT>" &
"<STYLE>body { display: none; }</STYLE>" &
"<TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>");
else
Protected_Strings.Reset (Last_CNA_CNS_Peername, Status.Peername (Request)); -- reset if matches
return Response.URL (Config.Get_Target_URL);
end if;
end Service;
end Worker_Echoes.Apple.Captive;
1. Thank you very much OCTAGRAM for the detailed answer. Comments do not support formatting, so I will answer in a separate post.
2. I got it somehow easier. Maybe I'm missing something, but it works stably on several iPhones with different IOS versions (8-10).
Python code:
# функция обрабатывает URL /hotspot-.*.html
# для запоминания пользователей я использую redis
def get(self):
# если мы помним, что этот пользователь уже обращался на hotspot-.*.html,
# подделываем ответ captive.apple.com
if self.redis_conn.get('apple_wispr:some_user_marker'):
self.finish(
'''
<HTML>
<HEAD><TITLE>Success</TITLE></HEAD>
<BODY>
<a href="http://ya.ru">This link will open in Safari</a>
</BODY>
</HTML>
'''
)
# иначе, запоминаем этого пользователя на 10 секунд
# и отдаем заглушку. всплывет CNA
else:
self.redis_conn.set('apple_wispr:some_user_marker', 1, 10)
self.finish('<html></html>')
"GET /hotspot-detect.html HTTP/1.0" 200 13 "-" "CaptiveNetworkSupport-346 wispr"
"GET /hotspot-detect.html HTTP/1.1" 200 273 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2 like Mac OS X) AppleWebKit/602.3.12 (KHTML, like Gecko) Mobile/14C92"
"GET /hotspot-detect.html HTTP/1.0" 200 273 "-" "CaptiveNetworkSupport-346 wispr"
Well, that's right, using the A-record ALWAYS when you request the captive.apple.com domain, send the gadget to your server (previously registered in walled-garden, of course). And the server needs to be taught, with such requests, to look in the records of open radius sessions for the presence of a device poppy there (and the user's login too), so that if there is an active session (for example, if the last one started less than N hours before the maximum you set) - answer the gadget always "Success" and send in peace. In theory, then iPhones will be less "nervous" in the hands of users, being ALREADY authorized by the MAC-cookie or HTTP-cookie mechanisms.
M? :)
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question