L
L
Leonid2016-09-27 18:12:12
iOS
Leonid, 2016-09-27 18:12:12

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

3 answer(s)
O
OCTAGRAM, 2017-01-19
@zzevaka

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"

The first two answers are to show wispr such a bad thing that he wants to intervene. wispr ends with "wispr" on both Mac OS X and iOS. Immediately 403 or 200 without Success somehow doesn’t work very well, but 302 does, but since 302, then something needs to be answered at a different address. Then CNS is launched (no Safari), and a page is given to it, on which there is Success and a script that, through the floorseconds will create a normal (without _blank) link with an absolute URL (which is with a two) and click on it with a click(). When a page is served on a stateful server, this computer is marked. Either following the link, or Success and code 200 on the page, or it’s just done every time after the first opening, until you figure it out, some of this forces you to make another wispr request, and this time the most usual Success is given to the marked computer, and when the link is followed in half a second, CNS will already be in the “Done” state and will direct the transition to the real Safari, and close itself. When a link is found to be opened by real Safari, the mark from the computer is reset.
It happens that it gets stuck in CNA, but after increasing the delay by a second, it seems to have returned to normal.
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;

L
Leonid, 2017-02-16
@zzzevaka

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>')

As you can see, even without "success" in the body it works.
In nginx it looks like this:
"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"

3. Actually, what was my problem:
Before I had to respond clumsily to the first wispr request, and to forge the original response to captive.apple.com for all the others, I did it even before I asked this question.
The problem was this: when you clicked on the link, the device disconnected from the WiFi network and went to safari via the mobile Internet. This is despite the fact that CNA already had a "Done" button and it seems like CNA has already been deceived.
When it dawned on me, I cursed myself for a long time for stupidity. Reason:
To redirect the user to the authorization server, I use the Mikrotik ip hotpsot functionality.
When ios tries to reach captive.apple.com, Mikrotik redirects it (using HTTP 302) to the authorization server. And so with EVERY REQUEST.
IOS ALWAYS received 302 on original requests. Interestingly, it partially accepted responses after the redirect as normal (the done button appeared).
Solution: redirect requests to authorization server without using HTTP.
I registered the captive.apple.com A-record in Mikrotik's DNS on my server and added a handler to /hotspot-.*.html on the server
And it looks like cheers.

M
master_fox, 2018-02-10
@master_fox

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 question

Ask a Question

731 491 924 answers to any question